web-dev-qa-db-ja.com

ARReferenceImageがカメラのビューに表示されなくなったかどうかを確認します

ARReferenceImage がカメラのビューに表示されなくなったかどうかを確認したいと思います。現時点では、画像のノードがカメラのビューにあるかどうかを確認できますが、 ARReferenceImage が別の画像で覆われている場合、または画像が削除されている場合、このノードはカメラのビューに表示されたままです。

func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
    guard let node = self.currentImageNode else { return }

    if let pointOfView = sceneView.pointOfView {
        let isVisible = sceneView.isNode(node, insideFrustumOf: pointOfView)
        print("Is node visible: \(isVisible)")
    }
}

そのため、画像のノードの可視性の代わりに、画像が表示されなくなったかどうかを確認する必要があります。しかし、これが可能かどうかはわかりません。最初のスクリーンショットは、下の画像が見つかったときに追加される3つのボックスを示しています。見つかった画像が覆われたら(スクリーンショット2を参照)、ボックスを削除したいと思います。

Found image with boxes

Convered image with boxes

16
KNV

私は問題を解決することができました! Maybe1のコードと彼のコンセプトを少し使用して問題を解決しましたが、方法は異なります。次のコード行は、引き続き画像認識を再アクティブ化するために使用されます。

// Delete anchor from the session to reactivate the image recognition
sceneView.session.remove(anchor: anchor) 

説明させてください。最初に、いくつかの変数を追加する必要があります。

// The scnNodeBarn variable will be the node to be added when the barn image is found. Add another scnNode when you have another image.    
var scnNodeBarn: SCNNode = SCNNode()
// This variable holds the currently added scnNode (in this case scnNodeBarn when the barn image is found)     
var currentNode: SCNNode? = nil
// This variable holds the UUID of the found Image Anchor that is used to add a scnNode    
var currentARImageAnchorIdentifier: UUID?
// This variable is used to call a function when there is no new anchor added for 0.6 seconds    
var timer: Timer!

以下のコメント付きの完全なコード。

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

    let referenceImage = imageAnchor.referenceImage

    // The following timer fires after 0.6 seconds, but everytime when there found an anchor the timer is stopped.
    // So when there is no ARImageAnchor found the timer will be completed and the current scene node will be deleted and the variable will set to nil
    DispatchQueue.main.async {
        if(self.timer != nil){
            self.timer.invalidate()
        }
        self.timer = Timer.scheduledTimer(timeInterval: 0.6 , target: self, selector: #selector(self.imageLost(_:)), userInfo: nil, repeats: false)
    }

    // Check if there is found a new image on the basis of the ARImageAnchorIdentifier, when found delete the current scene node and set the variable to nil
    if(self.currentARImageAnchorIdentifier != imageAnchor.identifier &&
        self.currentARImageAnchorIdentifier != nil
        && self.currentNode != nil){
            //found new image
            self.currentNode!.removeFromParentNode()
            self.currentNode = nil
    }

    updateQueue.async {

        //If currentNode is nil, there is currently no scene node
        if(self.currentNode == nil){

            switch referenceImage.name {
                case "barn":
                    self.scnNodeBarn.transform = node.transform
                    self.sceneView.scene.rootNode.addChildNode(self.scnNodeBarn)
                    self.currentNode = self.scnNodeBarn
                default: break
            }

        }

        self.currentARImageAnchorIdentifier = imageAnchor.identifier

        // Delete anchor from the session to reactivate the image recognition
        self.sceneView.session.remove(anchor: anchor)
    }

}

タイマーが終了したら、新しいARImageAnchorが見つからなかったことを示すノードを削除します。

@objc
    func imageLost(_ sender:Timer){
        self.currentNode!.removeFromParentNode()
        self.currentNode = nil
    }

このようにして、現在追加されているscnNodeは、画像が覆われたとき、または新しい画像が見つかったときに削除されます。

enter image description here

残念ながら、この解決策では、次の理由により画像の位置決めの問題を解決できません。

ARKitは、検出された各画像の位置や向きの変更を追跡しません。

11
KNV

これは現在可能だとは思わない。

ARエクスペリエンスドキュメントでの画像の認識 から:

仮想コンテンツの開始点として検出された画像を使用するようにAR体験を設計します。

ARKitは、検出された各画像の位置または向きの変更を追跡しません。検出された画像に添付されたままの仮想コンテンツを配置しようとすると、そのコンテンツが正しく配置されていないように見える場合があります。代わりに、動的なシーンを開始するための基準フレームとして検出された画像を使用します。


IOS 12.0の新しい回答

ARKit 2.0とiOS 12では、ARImageTrackingConfigurationまたはARWorldTrackingConfiguration.detectionImagesプロパティは、画像の位置も追跡するようになりました。

Apple ARImageTrackingConfigurationのドキュメントには、両方の方法の利点がリストされています。

ARImageTrackingConfigurationを使用すると、ARKitは、世界に対するデバイスの動きを追跡するのではなく、カメラから見た既知の2D画像の動きを検出および追跡するだけで3D空間を確立します。 ARWorldTrackingConfigurationも画像を検出できますが、各構成には独自の長所があります。

  • ワールドトラッキングは、画像のみのトラッキングよりもパフォーマンスコストが高いため、ARImageTrackingConfigurationを使用して、セッションでより多くの画像を一度に確実にトラッキングできます。

  • 画像のみのトラッキングを使用すると、既知の画像がカメラから見える場合にのみ、それらの画像に仮想コンテンツを固定できます。画像検出によるワールドトラッキングを使用すると、既知の画像を使用して仮想コンテンツを3Dワールドに追加でき、画像が表示されなくなった後でもワールド空間でのコンテンツの位置を追跡し続けます。

  • ワールドトラッキングは、安定した不動の環境で最適に機能します。画像のみのトラッキングを使用して、より多くの状況で既知の画像に仮想コンテンツを追加できます。たとえば、動く地下鉄の車内の広告などです。

7
jlsiewert

追跡している画像が現在ARKitによって追跡されていないかどうかを確認する正しい方法は、アンカー関数のdidUpdateノードのARImageAnchorで「isTracked」プロパティを使用することです。

そのために、次の構造体を使用します。

struct TrackedImage {
    var name : String
    var node : SCNNode?
}

そして、すべての画像の名前を持つその構造体の配列。

var trackedImages : [TrackedImage] = [ TrackedImage(name: "image_1", node: nil) ]

次に、アンカーのdidAddノードで、シーンに新しいコンテンツを設定し、trackedImagesの配列内の対応する要素にノードを追加します

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
    // Check if the added anchor is a recognized ARImageAnchor
    if let imageAnchor = anchor as? ARImageAnchor{
        // Get the reference ar image
        let referenceImage = imageAnchor.referenceImage
        // Create a plane to match the detected image.
        let plane = SCNPlane(width: referenceImage.physicalSize.width, height: referenceImage.physicalSize.height)
        plane.firstMaterial?.diffuse.contents = UIColor(red: 1, green: 1, blue: 1, alpha: 0.5)
        // Create SCNNode from the plane
        let planeNode = SCNNode(geometry: plane)
        planeNode.eulerAngles.x = -.pi / 2
        // Add the plane to the scene.
        node.addChildNode(planeNode)
        // Add the node to the tracked images
        for (index, trackedImage) in trackedImages.enumerated(){
            if(trackedImage.name == referenceImage.name){
                trackedImage[index].node = planeNode
            }
        }
    }
}

最後に、アンカー関数のdidUpdateノードで、配列内のアンカー名を検索し、プロパティisTrackedがfalseであるかどうかを確認します。

func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
    var trackedImages : [TrackedImage] = [ TrackedImage(name: "image_1", node: nil) ]
    if let imageAnchor = anchor as? ARImageAnchor{
        // Search the corresponding node for the ar image anchor
        for (index, trackedImage) in trackedImages.enumerated(){
            if(trackedImage.name == referenceImage.name){
                // Check if track is lost on ar image
                if(imageAnchor.isTracked){
                    // The image is being tracked
                    trackedImage.node?.isHidden = false // Show or add content
                }else{
                    // The image is lost
                    trackedImage.node?.isHidden = true // Hide or delete content
                }
                break
            }
        }
    }
}

このソリューションは、複数の画像を同時に追跡し、それらの画像のいずれかが失われたときを知りたいときに機能します。

注:このソリューションが機能するには、AR構成のmaximumNumberOfTrackedImagesをゼロ以外の数値に設定する必要があります。

6
Abraham Torres

その価値のために、私は何時間もかけて画像参照を常にチェックする方法を見つけようとしました。 didUpdate関数が答えでした。次に、.isTrackedプロパティを使用して参照画像が追跡されていることをテストするだけです。その時点で、.isHiddenプロパティをtrueまたはfalseに設定できます。ここに私の例があります:

func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
        let trackedNode = node

        if let imageAnchor = anchor as? ARImageAnchor{

        if (imageAnchor.isTracked) {
            trackedNode.isHidden = false
            print("\(trackedNode.name)")

        }else {
            trackedNode.isHidden = true

            //print("\(trackedImageName)")
            print("No image in view")

        }
        }
    }
1
vx2ko

ARエクスペリエンスドキュメントでの画像の認識 から:

ARKitは、セッション構成のdetectionImages配列内の参照画像ごとに1回だけ、画像アンカーをセッションに追加します。画像が検出されたときにARエクスペリエンスがシーンに仮想コンテンツを追加する場合、そのアクションはデフォルトで1回のみ発生します。ユーザーがアプリを再起動せずにそのコンテンツを再び体験できるようにするには、セッションのremove(anchor :)メソッドを呼び出して、対応するARImageAnchorを削除します。アンカーが削除された後、ARKitは次に画像を検出したときに新しいアンカーを追加します。

だから、あなたの場合の回避策を見つけることができます:

検出されたARImageAnchorと関連付けられた仮想コンテンツを保存する構造であるとしましょう:

_struct ARImage {
    var anchor: ARImageAnchor
    var node: SCNNode
}
_

次に、renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor)が呼び出されると、検出された画像をARImageの一時リストに保存します。

_...

var tmpARImages: [ARImage] = []

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

        // If the ARImage does not exist
        if !tmpARImages.contains(where: {$0.anchor.referenceImage.name == referenceImage.name}) {
            let virtualContent = SCNNode(...)
            node.addChildNode(virtualContent)

            tmpARImages.append(ARImage(anchor: imageAnchor, node: virtualContent))
        }


        // Delete anchor from the session to reactivate the image recognition
        sceneView.session.remove(anchor: anchor)    
}
_

カメラのビューが画像/マーカーの外を指しているのを理解している場合、デリゲート関数は無限にループします...(セッションからアンカーを削除したため)。

画像認識ループ、検出された画像をtmpリストに保存し、sceneView.isNode(node, insideFrustumOf: pointOfView)関数を組み合わせて、検出された画像/マーカーが表示されなくなったかどうかを判断します。

私はそれが明確だったことを願っています...

0
YMonnier

私はあなたの質問を理解したかどうかは完全にはわかりません(謝罪します)が、もし私が知っていれば、おそらくこれが役立つかもしれません...

insideOfFrustumが正しく機能するためには、それらが機能するためにノードに関連付けられたSCNGeometryである必要があります(SCNNodeだけでは十分ではありません)。

たとえば、delegateコールバックで次のようなことを行い、追加されたSCNNodeを配列に保存する場合:

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {

    //1. If Out Target Image Has Been Detected Than Get The Corresponding Anchor
    guard let currentImageAnchor = anchor as? ARImageAnchor else { return }

    //2. Print The Anchor ID & It's Associated Node
    print("""
        Anchor With ID Has Been Detected \(currentImageAnchor.identifier)
        Associated Node Details = \(node)
        """)


    //3. Store The Node
    imageTargets.append(node)
}

そして、insideOfFrustumメソッドを使用します。99%の確率で、ノードが表示されるべきではないとわかっていても、ノードが表示されていると表示されます。

ただし、次のような処理を行う場合(これにより、透明なマーカーノード、たとえばジオメトリを持つノードを作成します):

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {

    //1. If Out Target Image Has Been Detected Than Get The Corresponding Anchor
    guard let currentImageAnchor = anchor as? ARImageAnchor else { return }

    //2. Print The Anchor ID & It's Associated Node
    print("""
        Anchor With ID Has Been Detected \(currentImageAnchor.identifier)
        Associated Node Details = \(node)
        """)


    //3. Create A Transpanrent Geometry
    node.geometry = SCNSphere(radius: 0.1)
    node.geometry?.firstMaterial?.diffuse.contents = UIColor.clear

    //3. Store The Node
    imageTargets.append(node)
}

そして、次のメソッドを呼び出して、ARReferenceImageがinViewであるかどうかを検出します:

func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {

    //1. Get The Current Point Of View
    guard let pointOfView = augmentedRealityView.pointOfView else { return }

    //2. Loop Through Our Image Target Markers
    for addedNode in imageTargets{

        if  augmentedRealityView.isNode(addedNode, insideFrustumOf: pointOfView){
            print("Node Is Visible")
        }else{
            print("Node Is Not Visible")
        }

    }

}

SCNNodeが別のSCNNodeによって隠されているという点については、Apple Docsは、inViewOfFrostrum

オクルージョンテストを実行しません。つまり、テストされたノードが指定された視錐台内にある場合、そのノードのコンテンツが他のジオメトリによって隠されているかどうかに関係なく、trueを返します。

繰り返しますが、私があなたを正しく理解していないならおaびしますが、うまくいけばそれがある程度役立つかもしれません...

更新:

これであなたの質問を完全に理解しました。これは不可能であることを@orangenkopfに同意します。ドキュメントの状態として:

ARKitは、検出された各画像の位置や向きの変更を追跡しません。

0
BlackMirrorz

このコードは、デバイスを厳密に水平または垂直に持っている場合にのみ機能します。 iPhoneを傾けたままにしたり、傾斜を開始したりすると、このコードは機能しません。

func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {

    //1. Get The Current Point Of View
    guard let pointOfView = augmentedRealityView.pointOfView else { return }

    //2. Loop Through Our Image Target Markers
    for addedNode in imageTargets{

        if  augmentedRealityView.isNode(addedNode, insideFrustumOf: pointOfView){
            print("Node Is Visible")
        }else{
            print("Node Is Not Visible")
        }

    }

}