Je voudrais vérifier si l'image ARReferenceImage n'est plus visible dans la vue de l'appareil photo. Pour l'instant, je peux vérifier si le nœud de l'image est dans la vue de la caméra, mais ce nœud est toujours visible dans la vue de la caméra lorsque ARReferenceImage est recouvert d'une autre image ou lorsque l'image est supprimée.
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)")
}
}
Je dois donc vérifier si l'image n'est plus visible à la place de la visibilité des nœuds de l'image. Mais je ne peux pas savoir si c'est possible. La première capture d'écran montre trois boîtes qui sont ajoutées lorsque l'image ci-dessous est trouvée. Lorsque l'image trouvée est couverte (voir capture d'écran 2), j'aimerais supprimer les boîtes.
J'ai réussi à résoudre le problème! Utilisé un peu du code de Maybe1 et de son concept pour résoudre le problème, mais d'une manière différente. La ligne de code suivante est toujours utilisée pour réactiver la reconnaissance d’image.
// Delete anchor from the session to reactivate the image recognition
sceneView.session.remove(anchor: anchor)
Laisse-moi expliquer. Nous devons d’abord ajouter quelques variables.
// 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!
Le code complet avec les commentaires ci-dessous.
/// - 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)
}
}
Supprimez le nœud lorsque le minuteur est terminé en indiquant qu'aucun nouvel ARImageAnchor n'a été trouvé.
@objc
func imageLost(_ sender:Timer){
self.currentNode!.removeFromParentNode()
self.currentNode = nil
}
De cette manière, le scnNode actuellement ajouté sera supprimé lorsque l’image sera recouverte ou lorsqu’une nouvelle image sera trouvée.
Cette solution ne résout malheureusement pas le problème de positionnement des images pour les raisons suivantes:
ARKit ne suit pas les modifications apportées à la position ou à l’orientation de chaque image détectée.
Je ne pense pas que ce soit actuellement possible.
À partir de Reconnaissance des images dans une documentation AR Experience :
Concevez votre expérience AR pour utiliser les images détectées comme point de départ du contenu virtuel.
ARKit ne suit pas les modifications apportées à la position ou à l’orientation de chaque image détectée. Si vous essayez de placer un contenu virtuel qui reste attaché à une image détectée, ce contenu peut ne pas sembler rester en place correctement. Utilisez plutôt les images détectées comme cadre de référence pour démarrer une scène dynamique.
ARKit 2.0 et iOS 12 ajoutent enfin cette fonctionnalité, soit via ARImageTrackingConfiguration
, soit via la propriété ARWorldTrackingConfiguration.detectionImages
qui permet également de suivre également la position des images.
La documentation Apple sur ARImageTrackingConfiguration
répertorie les avantages des deux méthodes:
Avec ARImageTrackingConfiguration, ARKit crée un espace 3D non pas en suivant le mouvement du périphérique par rapport au monde, mais uniquement en détectant et en suivant le mouvement des images 2D connues à la vue de la caméra. ARWorldTrackingConfiguration peut également détecter des images, mais chaque configuration a ses propres forces:
Le suivi mondial a un coût de performance supérieur au suivi uniquement par image. Ainsi, votre session peut suivre plusieurs images à la fois de manière fiable avec ARImageTrackingConfiguration.
Le suivi uniquement d'image vous permet d'ancrer du contenu virtuel aux images connues uniquement lorsque ces images sont visibles de la caméra. Le suivi du monde avec détection d'images vous permet d'utiliser des images connues pour ajouter du contenu virtuel au monde 3D et continue de suivre la position de ce contenu dans l'espace mondial même après le retrait de l'image.
Le suivi du monde fonctionne mieux dans un environnement stable et non mobile. Vous pouvez utiliser le suivi par image uniquement pour ajouter du contenu virtuel aux images connues dans davantage de situations, par exemple, une publicité dans un wagon de métro en mouvement.
La méthode correcte pour vérifier si une image que vous suivez n'est pas actuellement suivie par ARKit consiste à utiliser la propriété "isTracked" dans ARImageAnchor sur le noeud didUpdate pour la fonction d'ancrage.
Pour cela, j'utilise le prochain struct:
struct TrackedImage {
var name : String
var node : SCNNode?
}
Et puis un tableau de cette structure avec le nom de toutes les images.
var trackedImages : [TrackedImage] = [ TrackedImage(name: "image_1", node: nil) ]
Ensuite, dans le nœud didAdd pour l'ancrage, définissez le nouveau contenu sur la scène et ajoutez également le nœud à l'élément correspondant dans le tableau de 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
}
}
}
}
Enfin, dans le nœud didUpdate pour la fonction d'ancrage, nous recherchons le nom de l'ancre dans notre tableau et vérifions si la propriété isTracked est 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
}
}
}
}
Cette solution fonctionne lorsque vous souhaitez suivre plusieurs images en même temps et savoir quand l'une d'entre elles est perdue.
Remarque: Pour que cette solution fonctionne, le nombre maximalNumberOfTrackedImages dans la configuration AR doit être défini sur un nombre différent de zéro.
À partir de Reconnaissance des images dans une documentation AR Experience :
ARKit ajoute une image d'ancrage à une session, exactement une fois pour chaque image de référence dans le tableau de détection d'images de la configuration de la session . Si votre expérience RA ajoute un contenu virtuel à la scène lorsqu'une image est détecté, cette action se produira par défaut une seule fois. Autoriser l’utilisateur à revivre ce contenu sans avoir à redémarrer votre application, appelez la méthode remove (anchor :) de la session pour supprimer le fichier .__ correspondant. ARImageAnchor. Une fois l'ancre retirée, ARKit ajoutera un nouveau ancre la prochaine fois qu'il détecte l'image.
Alors, peut-être que vous pouvez trouver une solution de contournement pour votre cas:
Disons que nous sommes cette structure qui enregistre notre ARImageAnchor
détectée et le contenu virtuel associé:
struct ARImage {
var anchor: ARImageAnchor
var node: SCNNode
}
Ensuite, lorsque la renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor)
est appelée, vous enregistrez l'image détectée dans une liste temporaire de 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)
}
Si vous avez compris, lorsque la vue de votre caméra pointe hors de l'image/du marqueur, la fonction de délégué effectuera une boucle infinie ... (car nous avons retiré l'ancre de la session).
L’idée sera de combiner la boucle de reconnaissance d’image, l’image détectée enregistrée dans la liste tmp et la fonction sceneView.isNode(node, insideFrustumOf: pointOfView)
pour déterminer si l’image/le marqueur détecté n’est plus visualisé.
J'espère que c'était clair ...
Je ne suis pas tout à fait sûr d'avoir bien compris ce que vous demandiez (excusez-moi), mais si j'ai bien compris, cela pourrait peut-être aider ...
Il semble que pour que insideOfFrustum
fonctionne correctement, il doit être associé à un SCNGeometry
associé au noeud afin que cela fonctionne (un SCNNode seul ne suffira pas).
Par exemple, si nous faisons quelque chose comme ceci dans le callback delegate
et sauvegardons la SCNNode
ajoutée dans un tableau:
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)
}
Et ensuite, utilisez la méthode insideOfFrustum
. 99% du temps, le nœud est visible même si nous savons qu'il ne devrait pas l'être.
Cependant, si nous faisons quelque chose comme ceci (nous créons un noeud de marqueur transparent, par exemple un noeud ayant une géométrie):
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)
}
Et ensuite, appelez la méthode suivante, elle détecte si la ARReferenceImage
est 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")
}
}
}
En ce qui concerne votre autre point concernant un nœud SCNNode obstrué par un autre, le Apple Docs
indique que le inViewOfFrostrum
:
n'effectue pas de test d'occlusion. C'est-à-dire qu'il revient true si le nœud testé se situe dans le tronc de visualisation spécifié que le contenu de ce noeud soit masqué par d’autres géométrie.
Encore une fois, excusez-moi si je ne vous ai pas bien compris, mais j'espère que cela aidera dans une certaine mesure ...
Mettre à jour:
Maintenant que je comprends parfaitement votre question, je suis d’accord avec @orangenkopf pour dire que ce n’est pas possible. Depuis l'état de la documentation:
ARKit ne suit pas les modifications apportées à la position ou à l’orientation de chaque image détectée.
Ce code ne fonctionne que si vous tenez l'appareil strictement à l'horizontale ou à la verticale. Si vous tenez l'iPhone incliné ou si vous commencez à l'incliner si, ce code ne fonctionne pas:
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")
}
}
}