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を参照)、ボックスを削除したいと思います。
私は問題を解決することができました! 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は、画像が覆われたとき、または新しい画像が見つかったときに削除されます。
残念ながら、この解決策では、次の理由により画像の位置決めの問題を解決できません。
ARKitは、検出された各画像の位置や向きの変更を追跡しません。
これは現在可能だとは思わない。
仮想コンテンツの開始点として検出された画像を使用するようにAR体験を設計します。
ARKitは、検出された各画像の位置または向きの変更を追跡しません。検出された画像に添付されたままの仮想コンテンツを配置しようとすると、そのコンテンツが正しく配置されていないように見える場合があります。代わりに、動的なシーンを開始するための基準フレームとして検出された画像を使用します。
ARKit 2.0とiOS 12では、ARImageTrackingConfiguration
またはARWorldTrackingConfiguration.detectionImages
プロパティは、画像の位置も追跡するようになりました。
Apple ARImageTrackingConfiguration
のドキュメントには、両方の方法の利点がリストされています。
ARImageTrackingConfigurationを使用すると、ARKitは、世界に対するデバイスの動きを追跡するのではなく、カメラから見た既知の2D画像の動きを検出および追跡するだけで3D空間を確立します。 ARWorldTrackingConfigurationも画像を検出できますが、各構成には独自の長所があります。
ワールドトラッキングは、画像のみのトラッキングよりもパフォーマンスコストが高いため、ARImageTrackingConfigurationを使用して、セッションでより多くの画像を一度に確実にトラッキングできます。
画像のみのトラッキングを使用すると、既知の画像がカメラから見える場合にのみ、それらの画像に仮想コンテンツを固定できます。画像検出によるワールドトラッキングを使用すると、既知の画像を使用して仮想コンテンツを3Dワールドに追加でき、画像が表示されなくなった後でもワールド空間でのコンテンツの位置を追跡し続けます。
ワールドトラッキングは、安定した不動の環境で最適に機能します。画像のみのトラッキングを使用して、より多くの状況で既知の画像に仮想コンテンツを追加できます。たとえば、動く地下鉄の車内の広告などです。
追跡している画像が現在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
をゼロ以外の数値に設定する必要があります。
その価値のために、私は何時間もかけて画像参照を常にチェックする方法を見つけようとしました。 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")
}
}
}
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)
関数を組み合わせて、検出された画像/マーカーが表示されなくなったかどうかを判断します。
私はそれが明確だったことを願っています...
私はあなたの質問を理解したかどうかは完全にはわかりません(謝罪します)が、もし私が知っていれば、おそらくこれが役立つかもしれません...
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は、検出された各画像の位置や向きの変更を追跡しません。
このコードは、デバイスを厳密に水平または垂直に持っている場合にのみ機能します。 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")
}
}
}