Пространственные привязки Azure / привязки мира Unity теряют позицию

Я работаю над простым приложением Unity, тестирующим пространственные привязки Azure на HoloLens. Я начал с этого примера (https://github.com/Azure/azure-spatial-anchors-samples) и немного изменили его, чтобы создать несколько якорей.

В некоторых тестовых сессиях я наблюдал, что закрепленные объекты внезапно теряли свою позицию и перемещались примерно на 10 метров или более.

Поскольку я понимаю HoloLens и смешанную реальность, положение камеры отслеживается с помощью визуальной одометрии или, скорее, алгоритмов SLAM, поэтому нормально, что поза устройства смещается со временем, как и якоря. Но я не ожидал такого огромного сдвига. Кроме того, я ожидал, что якоря вернутся на свои места в тот момент, когда объекты в районе якорей снова станут видны для камеры устройства. Но так бывает не всегда. Иногда якоря возвращаются в исходное положение, когда элементы снова становятся видимыми, но иногда это ничего не меняет в неправильных положениях.

Это код:

using Microsoft.Azure.SpatialAnchors;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.XR.WSA;
using UnityEngine.XR.WSA.Input;
using System.Linq;
using System.IO;
using UnityEditor;

public class AzureSpatialAnchorsScript : MonoBehaviour
{
    /// <summary>
    /// The sphere prefab.
    /// </summary>
    public GameObject spherePrefab;

    /// <summary>
    /// Set this string to the Spatial Anchors account id provided in the Spatial Anchors resource.
    /// </summary>
    protected string SpatialAnchorsAccountId = "xxxxxxxxxxxxxxxx";

    /// <summary>
    /// Set this string to the Spatial Anchors account key provided in the Spatial Anchors resource.
    /// </summary>
    protected string SpatialAnchorsAccountKey = "yyyyyyyyyyyyyyyyyyyyyyy";

    /// <summary>
    /// Our queue of actions that will be executed on the main thread.
    /// </summary>
    private readonly Queue<Action> dispatchQueue = new Queue<Action>();

    /// <summary>
    /// Use the recognizer to detect air taps.
    /// </summary>
    private GestureRecognizer recognizer;

    protected CloudSpatialAnchorSession cloudSpatialAnchorSession;

    /// <summary>
    /// The CloudSpatialAnchor that we either 1) placed and are saving or 2) just located.
    /// </summary>
    protected CloudSpatialAnchor currentCloudAnchor;

    /// <summary>
    /// True if we are creating + saving an anchor
    /// </summary>
    protected bool tapExecuted = false;

    /// <summary>
    /// The IDs of the CloudSpatialAnchor that were saved. Use it to find the CloudSpatialAnchors
    /// </summary>
    protected Dictionary<string, GameObject> cloudSpatialAnchorIdsObjects = new Dictionary<string, GameObject> { };

    protected IList<string> anchorIds = new List<string>();

    /// <summary>
    /// The sphere rendered to show the position of the CloudSpatialAnchor.
    /// </summary>
    protected Material sphereMaterial;

    /// <summary>
    /// Indicate if we are ready to save an anchor. We can save an anchor when value is greater than 1.
    /// </summary>
    protected float recommendedForCreate = 0;

    private string pathName;

    // Start is called before the first frame update
    void Start()
    {
        Application.SetStackTraceLogType(LogType.Log, StackTraceLogType.None);

        recognizer = new GestureRecognizer();

        recognizer.StartCapturingGestures();

        recognizer.SetRecognizableGestures(GestureSettings.Tap);

        recognizer.Tapped += HandleTap;

        InitializeSession();

        string FileName = "ids.txt";
        pathName = Path.Combine(Application.persistentDataPath, FileName);
        getIds();

        if (anchorIds.Count > 0)
        {
            CreateWatcher(anchorIds.ToArray());
        }
    }

    // Update is called once per frame
    void Update()
    {
        lock (dispatchQueue)
        {
            if (dispatchQueue.Count > 0)
            {
                dispatchQueue.Dequeue()();
            }
        }
    }

    /// <summary>
    /// Queues the specified <see cref="Action"/> on update.
    /// </summary>
    /// <param name="updateAction">The update action.</param>
    protected void QueueOnUpdate(Action updateAction)
    {
        lock (dispatchQueue)
        {
            dispatchQueue.Enqueue(updateAction);
        }
    }

    /// <summary>
    /// Cleans up objects.
    /// </summary>
    public void CleanupObjects()
    {
        if (cloudSpatialAnchorIdsObjects != null)
        {
            cloudSpatialAnchorIdsObjects = new Dictionary<string, GameObject>();
        }

        if (sphereMaterial != null)
        {
            Destroy(sphereMaterial);
            sphereMaterial = null;
        }

        //currentCloudAnchor = null;
    }   

    /// <summary>
    /// Initializes a new CloudSpatialAnchorSession.
    /// </summary>
    void InitializeSession()
    {
        Debug.Log("ASA Info: Initializing a CloudSpatialAnchorSession.");

        if (string.IsNullOrEmpty(SpatialAnchorsAccountId))
        {
            Debug.LogError("No account id set.");
            return;
        }

        if (string.IsNullOrEmpty(SpatialAnchorsAccountKey))
        {
            Debug.LogError("No account key set.");
            return;
        }

        cloudSpatialAnchorSession = new CloudSpatialAnchorSession();

        cloudSpatialAnchorSession.Configuration.AccountId = SpatialAnchorsAccountId.Trim();
        cloudSpatialAnchorSession.Configuration.AccountKey = SpatialAnchorsAccountKey.Trim();

        cloudSpatialAnchorSession.LogLevel = SessionLogLevel.All;

        cloudSpatialAnchorSession.Error += CloudSpatialAnchorSession_Error;
        cloudSpatialAnchorSession.OnLogDebug += CloudSpatialAnchorSession_OnLogDebug;
        cloudSpatialAnchorSession.SessionUpdated += CloudSpatialAnchorSession_SessionUpdated;
        cloudSpatialAnchorSession.AnchorLocated += CloudSpatialAnchorSession_AnchorLocated;
        cloudSpatialAnchorSession.LocateAnchorsCompleted += CloudSpatialAnchorSession_LocateAnchorsCompleted;

        cloudSpatialAnchorSession.Start();

        Debug.Log("ASA Info: Session was initialized.");
    }

    void CreateWatcher(string[] cloudSpatialAnchorIds)
    {
        Debug.Log("ASA Info: We will look for placeded anchors.");

        // Create a Watcher to look for the anchor we created.
        AnchorLocateCriteria criteria = new AnchorLocateCriteria();
        criteria.Identifiers = cloudSpatialAnchorIds;
        cloudSpatialAnchorSession.CreateWatcher(criteria);

        Debug.Log("ASA Info: Watcher created. Number of active watchers: " + cloudSpatialAnchorSession.GetActiveWatchers().Count);
    }

    private void CloudSpatialAnchorSession_Error(object sender, SessionErrorEventArgs args)
    {
        Debug.LogError("ASA Error: " + args.ErrorMessage);
    }

    private void CloudSpatialAnchorSession_OnLogDebug(object sender, OnLogDebugEventArgs args)
    {
        Debug.Log("ASA Log: " + args.Message);
        System.Diagnostics.Debug.WriteLine("ASA Log: " + args.Message);
    }

    private void CloudSpatialAnchorSession_SessionUpdated(object sender, SessionUpdatedEventArgs args)
    {
        Debug.Log("ASA Log: recommendedForCreate: " + args.Status.RecommendedForCreateProgress);
        recommendedForCreate = args.Status.RecommendedForCreateProgress;
    }

    private void CloudSpatialAnchorSession_AnchorLocated(object sender, AnchorLocatedEventArgs args)
    {
        switch (args.Status)
        {
            case LocateAnchorStatus.Located:
                Debug.Log("ASA Info: Anchor located! Identifier: " + args.Identifier);
                QueueOnUpdate(() =>
                {
                    // Create a green sphere.
                    GameObject spatialAnchorObj = GameObject.Instantiate(spherePrefab, Vector3.zero, Quaternion.identity) as GameObject;
                    spatialAnchorObj.AddComponent<WorldAnchor>();
                    sphereMaterial = spatialAnchorObj.GetComponent<MeshRenderer>().material;
                    sphereMaterial.color = Color.green;

                    // Get the WorldAnchor from the CloudSpatialAnchor and use it to position the sphere.
                    spatialAnchorObj.GetComponent<UnityEngine.XR.WSA.WorldAnchor>().SetNativeSpatialAnchorPtr(args.Anchor.LocalAnchor);

                    cloudSpatialAnchorIdsObjects.Add(args.Anchor.Identifier, spatialAnchorObj);

                    Debug.Log("Detected Pos: " + spatialAnchorObj.GetComponent<UnityEngine.XR.WSA.WorldAnchor>().transform.position.ToString("F4"));
                    Debug.Log("Detected Rot: " + spatialAnchorObj.GetComponent<UnityEngine.XR.WSA.WorldAnchor>().transform.rotation.ToString("F4"));

                    tapExecuted = false;
                });
                break;
            case LocateAnchorStatus.AlreadyTracked:
                Debug.Log("ASA Info: Anchor already tracked. Identifier: " + args.Identifier);
                break;
            case LocateAnchorStatus.NotLocated:
                Debug.Log("ASA Info: Anchor not located. Identifier: " + args.Identifier);
                break;
            case LocateAnchorStatus.NotLocatedAnchorDoesNotExist:
                Debug.LogError("ASA Error: Anchor not located does not exist. Identifier: " + args.Identifier);
                break;
        }
    }

    private void CloudSpatialAnchorSession_LocateAnchorsCompleted(object sender, LocateAnchorsCompletedEventArgs args)
    {
        Debug.Log("ASA Info: Locate anchors completed. Watcher identifier: " + args.Watcher.Identifier);
    }

    /// <summary>
    /// Called by GestureRecognizer when a tap is detected.
    /// </summary>
    /// <param name="eventArgs">The tap.</param>    
    public void HandleTap(TappedEventArgs eventArgs)
    {
        if (tapExecuted)
        {
            return;
        }
        tapExecuted = true;

        Debug.Log("ASA Info: We will create a new anchor.");

        //// Clean up any anchors that have been placed.
        //CleanupObjects();

        // Construct a Ray using forward direction of the HoloLens.
        Ray GazeRay = new Ray(eventArgs.headPose.position, eventArgs.headPose.forward);

        // Raycast to get the hit point in the real world.
        RaycastHit hitInfo;
        Physics.Raycast(GazeRay, out hitInfo, float.MaxValue);

        this.CreateAndSaveSphere(hitInfo.point);
    }

    /// <summary>
    /// Creates a sphere at the hit point, and then saves a CloudSpatialAnchor there.
    /// </summary>
    /// <param name="hitPoint">The hit point.</param>
    protected virtual void CreateAndSaveSphere(Vector3 hitPoint)
    {
        // Create a white sphere.
        GameObject spatialAnchorObj = GameObject.Instantiate(spherePrefab, hitPoint, Quaternion.identity) as GameObject;
        spatialAnchorObj.AddComponent<WorldAnchor>();
        sphereMaterial = spatialAnchorObj.GetComponent<MeshRenderer>().material;
        sphereMaterial.color = Color.white;
        Debug.Log("ASA Info: Created a local anchor.");

        // Create the CloudSpatialAnchor.
        currentCloudAnchor = new CloudSpatialAnchor();

        // Set the LocalAnchor property of the CloudSpatialAnchor to the WorldAnchor component of our white sphere.
        WorldAnchor worldAnchor = spatialAnchorObj.GetComponent<WorldAnchor>();
        if (worldAnchor == null)
        {
            throw new Exception("ASA Error: Couldn't get the local anchor pointer.");
        }

        // Save the CloudSpatialAnchor to the cloud.
        currentCloudAnchor.LocalAnchor = worldAnchor.GetNativeSpatialAnchorPtr();

        //cloudAnchor.AppProperties[@"x"] = @"frame";
        //cloudAnchor.AppProperties[@"label"] = @"my latest picture";

        Task.Run(async () =>
        {
            // Wait for enough data about the environment.
            while (recommendedForCreate < 1.0F)
            {
                await Task.Delay(330);
            }

            bool success = false;
            try
            {
                QueueOnUpdate(() =>
                {
                    // We are about to save the CloudSpatialAnchor to the Azure Spatial Anchors, turn it yellow.
                    sphereMaterial.color = Color.yellow;
                });

                await cloudSpatialAnchorSession.CreateAnchorAsync(currentCloudAnchor);
                success = currentCloudAnchor != null;

                if (success)
                {
                    // Record the identifier to locate.
                    string cloudAnchorId = currentCloudAnchor.Identifier;

                    QueueOnUpdate(() =>
                    {
                        // Turn the sphere blue.
                        sphereMaterial.color = Color.blue;
                    });

                    Debug.Log("ASA Info: Saved anchor to Azure Spatial Anchors! Identifier: " + cloudAnchorId);
                    //Debug.Log("Created " + cloudAnchorId + " at pos: " + worldAnchor.transform.position);
                    //Debug.Log("Created " + cloudAnchorId + "at rot: " + worldAnchor.transform.rotation);

                    anchorIds.Add(cloudAnchorId);
                    cloudSpatialAnchorIdsObjects.Add(cloudAnchorId, spatialAnchorObj);

                    WriteIds();
                }
                else
                {
                    sphereMaterial.color = Color.red;
                    Debug.LogError("ASA Error: Failed to save, but no exception was thrown.");
                }
            }
            catch (Exception ex)
            {
                QueueOnUpdate(() =>
                {
                    sphereMaterial.color = Color.red;
                });
                Debug.LogError("ASA Error: " + ex.Message);
            }

            // Allow the user to tap again to clear state and look for the anchor.
            tapExecuted = false;
        });
    }

    void WriteIds()
    {
        try
        {
            string fileContent = ""
                //= ReadString();
                ;

            foreach (string id in anchorIds)
            {
                fileContent += id + Environment.NewLine;
            }

            using (StreamWriter writer = new StreamWriter(new FileStream(pathName, FileMode.OpenOrCreate, FileAccess.Write)))
            {
                writer.Write(fileContent);
            }
        }
        catch (Exception e)
        {
            Debug.LogError(e);
        }
    }

    void getIds()
    {
        try
        {
            StreamReader reader = new StreamReader(pathName);

            string line;
            while ((line = reader.ReadLine()) != null)
            {
                anchorIds.Add(line);
            }

            reader.Close();
        }
        catch (FileNotFoundException e)
        {
            Debug.LogWarning("No AnchorId file found");
        }
    }
}

Что-то не так с тем, как создаются якоря, или это нормальное поведение?


person jokokojote    schedule 05.12.2019    source источник


Ответы (1)


Неудивительно, что иногда якоря теряют свое положение, и может случиться так, что некоторые из якорей перемещаются после восстановления трекинга, но не все из них. Было бы полезно добавить сценарий к объекту, который вы создаете для привязки, который показывает состояние отслеживания привязанной к нему привязки. Вот образец: -

using System;
using UnityEngine;
using UnityEngine.XR.WSA;

public class ShowTrackingState : MonoBehavior
{
    WorldAnchor worldAnchor = null;
    Material renderMaterial = null;
    bool isTracking = false;
    void Start()
    {
        renderMaterial = gameObject.GetComponent<Renderer>().material;
    }

    void OnDestroy()
    {
        if (renderMaterial != null)
        {
            Destroy(renderMaterial);
        }
    }

    void Update()
    {
        if (worldAnchor == null)
        {
            worldAnchor = gameObject.GetComponent<worldAnchor>();
        }

        if (worldAnchor == null)
        {
            isTracking = false;
        }
        else
        {
            isTracking = worldAnchor.isLocated;
        }

        renderMaterial.color = isTracking ? Color.red : Color.green;
    }
}

Кроме того, мягкое напоминание: часто забывают скрывать идентификатор и ключ учетной записи при размещении вопросов на форумах; но это небезопасно, поэтому вы можете удалить эту часть из фрагмента кода в вопросе :-)

person shreya-msft    schedule 27.03.2020