Tracking images from MutableRuntimeReferenceImageLibrary

Hello everyone. I am developing an augmented reality application. I am a newbie in Unity, so do not judge strictly if this is some kind of trivial question. The task is as follows: I have a server from which I download images and videos after authorization. Then the user can go to the AR scene. Accordingly, when scanning one of the downloaded images, the corresponding video should appear. For this approach, I use Mutable Runtime Reference Image Library. Images are added to the library after the scene is opened, but when scanning, it displays a warning that there is no video. It also does not display the name of the scanned image, but displays an empty value. For the test, I added one image to the regular image library (Reference Image Library), and added a video with the same name to the folder with downloaded videos. Everything works when scanning this image. That is, I do not work with Mutable Runtime Reference Image Library? If so, how can I do this? I am attaching a script for loading images into the library and a script for scanning images.

using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
using System.Collections;
using System.IO;

public class DynamicImageLoading : MonoBehaviour
{
    [SerializeField]
    private ARTrackedImageManager arTrackedImageManager;

    [SerializeField]
    private DynamicVideoManager dinamicVideoManager;

    private MutableRuntimeReferenceImageLibrary mutableLibrary;

    private string imagesFolder;

    void Start()
    {
        imagesFolder = Path.Combine(Application.persistentDataPath, "DownloadedImages");

        StartCoroutine(WaitForARSessionAndAddImages());
    }

    IEnumerator WaitForARSessionAndAddImages()
    {
        while (ARSession.state != ARSessionState.Ready && ARSession.state != ARSessionState.SessionTracking)
        {
            yield return null;
        }

        mutableLibrary = (MutableRuntimeReferenceImageLibrary)arTrackedImageManager.referenceLibrary;

        StartCoroutine(AddImagesToLibrary());
    }

    IEnumerator AddImagesToLibrary()
    {
        string[] pngFiles = Directory.GetFiles(imagesFolder, "*.png");
        string[] jpgFiles = Directory.GetFiles(imagesFolder, "*.jpg");

        string[] allImageFiles = new string[pngFiles.Length + jpgFiles.Length];
        pngFiles.CopyTo(allImageFiles, 0);
        jpgFiles.CopyTo(allImageFiles, pngFiles.Length);

        foreach (var imagePath in allImageFiles)
        {
            Texture2D texture = LoadImageFromPath(imagePath);

            if (texture != null)
            {
                string imageName = Path.GetFileNameWithoutExtension(imagePath);
                var jobHandle = mutableLibrary.ScheduleAddImageWithValidationJob(texture, imageName, 0.1f);

                while (!jobHandle.jobHandle.IsCompleted)
                {
                    yield return null;
                }

                if (jobHandle.jobHandle.IsCompleted)
                {
                    Debug.Log($"Image {imagePath} added to the library.");
                    for (int i = 0; i < mutableLibrary.count; i++)
                    {
                        var image = mutableLibrary[i];
                        Debug.Log($"Image in library: {image.name}");
                    }
                }
                else
                {
                    Debug.LogWarning($"Failed to add image {imagePath} to the library.");
                }
            }
        }

        dinamicVideoManager.enabled = true;
    }

    private Texture2D LoadImageFromPath(string path)
    {
        byte[] imageBytes = File.ReadAllBytes(path);
        Texture2D texture = new Texture2D(2, 2);
        texture.LoadImage(imageBytes);
        return texture;
    }
}
using System.Collections;
using System.IO;
using UnityEngine;
using UnityEngine.Video;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;

public class DynamicVideoManager : MonoBehaviour
{
    [SerializeField]
    private ARTrackedImageManager trackedImages;    

    void Start()
    {

    }

    void OnEnable()
    {
        trackedImages.trackablesChanged.AddListener(OnTrackedImagesChanged);
    }

    void OnDisable() => trackedImages.trackablesChanged.RemoveListener(OnTrackedImagesChanged);

    private void OnTrackedImagesChanged(ARTrackablesChangedEventArgs<ARTrackedImage> eventArgs)
    {
        foreach (var trackedImage in eventArgs.added)
        {
            string imageName = trackedImage.referenceImage.name;
            Debug.Log("Image detected: " + imageName);

            StartCoroutine(LoadVideoForImage(imageName, trackedImage.transform));
        }
    }

    private IEnumerator LoadVideoForImage(string imageName, Transform imageTransform)
    {
        string videosFolder = Path.Combine(Application.persistentDataPath, "DownloadedVideos");
        string videoPath = Path.Combine(videosFolder, imageName + ".mp4");

        if (System.IO.File.Exists(videoPath))
        {
            GameObject videoObject = GameObject.CreatePrimitive(PrimitiveType.Quad);
            videoObject.transform.localScale = new Vector3(0.5f, 0.5f, 0.5f);

            videoObject.transform.position = imageTransform.position + Vector3.up * 0.1f;
            videoObject.transform.rotation = imageTransform.rotation;

            VideoPlayer videoPlayer = videoObject.AddComponent<VideoPlayer>();
            videoPlayer.url = videoPath;
            videoPlayer.playOnAwake = false;

            videoPlayer.Prepare();
            while (!videoPlayer.isPrepared)
            {
                yield return null;
            }

            videoPlayer.Play();
        }
        else
        {
            Debug.LogWarning("Video file not found: " + videoPath);
        }
    }
}
1 Like

Did you test with XR Simulation or on device?

The text below is some part of my tutorial about AR Testing.


I find this: [5.1.0-pre.3] - 2023-02-06: Added support for mutable runtime reference image libraries to XR Simulation.

There is also such info in the docs:

XR Simulation can detect and track all simulated tracked images in an environment, even if you have not included their textures in your reference image library. To optionally bind a simulated tracked image to your reference image library, set the Image field of its Simulated Tracked Image component to reference a texture asset that is also used in the reference image library.

When XR Simulation detects images that are not part of the reference image library, the corresponding ARTrackedImage trackables will not contain a fully initialized referenceImage. Instead, the guid property of the referenceImage is set to zero, and its texture is set to null

There is BasicImageTracking Example Scene that uses Mutable Library in AR Foundation Samples that I just tested.

I duplicated the simulated environment, added one more tracked image on the scene and set the Image field as it mentioned before. I also added this in the protected override void OnTrackablesChanged() of ARTrackedImageManager.cs:

foreach (var newImage in added)
{
    Debug.Log(newImage.referenceImage.guid);
}

Indeed, there is no reaction to added list when a new image added to the library.

However, you can use updated list, which can trigger perfectly and track the first appearance after adding an image to the library:

foreach (var newImage in updated)
{
    Debug.Log(newImage.referenceImage.guid);
}

I can explain such behavior based on my research about Image Tracking:

possibly, the simulated environment assumes that AR Image is static in the environment, so once it appears in the camera view, it marks as static once because this is not a separate image - this is a container for image (SimulatedTrackedImage.cs) which is not moving.

So, if you want to trigger added list you need to add a new SimulatedTrackedImage.cs into the camera view: you can just place it before the scene start at some distance to the right/to the left from camera view.

Hello. Yes, I did the testing both in XP Simulation and on the device. I read your research and maybe did something similar on my own. I’m not sure if we understood each other, so let me describe my problem in more detail.

here is a link to a google drive with screenshots of the program in action (it turns out I can’t post more than one image here)
https://drive.google.com/drive/folders/1zAOdDXYsHApx7HIWFqYz1i-UvCkOXXPw?usp=sharing

In this picture you can see the presence of video in the downloaded videos folder
photo 0

Here you can see Simulation Environment. i placed 3 objects here and added image material to them
photo 1
photo 2
photo 3
photo 4
photo 5

After launching, the console displays information about the presence of images in the library (apparently in Mutable Runtime Reference Image Library)
photo 6

When you point the camera at an image that was originally added to the image library, a prefab with a specific video appears (the image name and the video name must match) (see the last two messages in the console)
photo 7

However, when you point the camera at an image that was not originally in the library, nothing happens. An empty name is displayed in the console and information that the program could not find a video with such an (empty) name
photo 8

The program works similarly with the third image

I have already sent both scripts, but I will forward them just in case

using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
using System.Collections;
using System.IO;

public class DynamicImageLoading : MonoBehaviour
{
    [SerializeField]
    private ARTrackedImageManager arTrackedImageManager;

    [SerializeField]
    private DynamicVideoManager dinamicVideoManager;

    private MutableRuntimeReferenceImageLibrary mutableLibrary;

    private string imagesFolder;

    void Start()
    {
        imagesFolder = Path.Combine(Application.persistentDataPath, "DownloadedImages");

        StartCoroutine(WaitForARSessionAndAddImages());
    }

    IEnumerator WaitForARSessionAndAddImages()
    {
        while ( ARSession.state != ARSessionState.Ready && ARSession.state != ARSessionState.SessionTracking)
        {
            yield return null;
        }

        mutableLibrary = (MutableRuntimeReferenceImageLibrary)arTrackedImageManager.referenceLibrary;

        StartCoroutine(AddImagesToLibrary());
    }

    IEnumerator AddImagesToLibrary()
    {
        string[] pngFiles = Directory.GetFiles(imagesFolder, "*.png");
        string[] jpgFiles = Directory.GetFiles(imagesFolder, "*.jpg");

        string[] allImageFiles = new string[pngFiles.Length + jpgFiles.Length];
        pngFiles.CopyTo(allImageFiles, 0);
        jpgFiles.CopyTo(allImageFiles, pngFiles.Length);

        foreach (var imagePath in allImageFiles)
        {
            Texture2D texture = LoadImageFromPath(imagePath);

            if (texture != null)
            {
                string imageName = Path.GetFileNameWithoutExtension(imagePath);
                var jobHandle = mutableLibrary.ScheduleAddImageWithValidationJob(texture, imageName, 0.1f);

                while (!jobHandle.jobHandle.IsCompleted)
                {
                    yield return null;
                }

                if (jobHandle.jobHandle.IsCompleted)
                {
                    for (int i = 0; i < mutableLibrary.count; i++)
                    {
                        var image = mutableLibrary[i];
                        Debug.Log($"Image in library: {image.name}");
                    }
                }
                else
                {
                    Debug.LogWarning($"Failed to add image {imagePath} to the library.");
                }
            }
        }

        yield return new WaitForSeconds(2f);

        dinamicVideoManager.enabled = true;
    }

    private Texture2D LoadImageFromPath(string path)
    {
        byte[] imageBytes = File.ReadAllBytes(path);
        Texture2D texture = new Texture2D(2, 2);
        texture.LoadImage(imageBytes);
        return texture;
    }
}
using System.Collections;
using System.IO;
using UnityEngine;
using UnityEngine.Video;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;

public class DynamicVideoManager : MonoBehaviour
{
    [SerializeField]
    private ARTrackedImageManager trackedImages;
    [SerializeField]
    private GameObject videoPrefab;

    void Start()
    {

    }

    void OnEnable() => trackedImages.trackablesChanged.AddListener(OnTrackedImagesChanged);
    void OnDisable() => trackedImages.trackablesChanged.RemoveListener(OnTrackedImagesChanged);

    private void OnTrackedImagesChanged(ARTrackablesChangedEventArgs<ARTrackedImage> eventArgs)
    {
        foreach (var trackedImage in eventArgs.added)
        {
            string imageName = trackedImage.referenceImage.name;
            Debug.Log("Image detected: " + imageName);

            StartCoroutine(LoadVideoForImage(imageName, trackedImage.transform));
        }
    }

    private IEnumerator LoadVideoForImage(string imageName, Transform imageTransform)
    {
        string videosFolder = Path.Combine(Application.persistentDataPath, "DownloadedVideos");
        string videoPath = Path.Combine(videosFolder, imageName + ".mp4");

        if (File.Exists(videoPath))
        {
            GameObject videoInstance = Instantiate(videoPrefab);
            videoInstance.transform.position = imageTransform.position;
            videoInstance.transform.rotation = imageTransform.rotation;

            VideoPlayer videoPlayer = videoInstance.GetComponentInChildren<VideoPlayer>();
            videoPlayer.url = videoPath;
            videoPlayer.playOnAwake = false;

            videoPlayer.Prepare();
            while (!videoPlayer.isPrepared)
            {
                yield return null;
            }
        }
        else
        {
            Debug.LogWarning("Video file not found: " + videoPath);
        }
    }
}

I am clearly missing some important detail, but I cannot figure out what it is

1 Like

I understood you immediately. As I said in the end, using XR Simulation, this can be related to what the camera sees at the start of the game.

Based on this:

When XR Simulation detects images that are not part of the reference image library, the corresponding ARTrackedImage trackables will not contain a fully initialized referenceImage. Instead, the guid property of the referenceImage is set to zero, and its texture is set to null

… I can guess that something is happened with Initializing the reference image.

Did you check GUIDs for all images? What is the result?

Why don’t you want to check the updated list as recommended? Using a simple boolean flag, you can check 1st appearance

If I understood you correctly, you advise adding the following lines of code

void OnChanged(ARTrackablesChangedEventArgs<ARTrackedImage> eventArgs)
{
    foreach (var newImage in eventArgs.added)
    {
        Debug.Log(newImage.referenceImage.guid);
    }

    foreach (var updatedImage in eventArgs.updated)
    {
        Debug.Log(updatedImage.referenceImage.guid);
    }
}

I added them, but nothing is output to the console. I did what you advised, right…? :smiling_face_with_tear:

1 Like

Sorry, I’m a dumbass. I forgot to change the event I need to subscribe to. This is what the console output:

So it doesn’t register images correctly at the beginning or later?

1 Like

I just clarified my tutorial with some moments, but there are no any breaking changes to what said above.

This is a video demo for you, with correct testing and initializing as it must be in real life ( Image field of its Simulated Tracked Image components are set):

1 Like

Hello R3vorm,

So the advice given by makaka-org is sound. I highly recommend you take a look at the sample – referenced by makaka-org – entitled “BasicImageTracking” which includes a button for dynamically adding some images to the set of reference images for tracking. The sample class named 'DynamicLibrary.cs" gives a simplistic demonstration.

I went ahead and tested this sample on device and in simulation. On device the behavior is working as expected, but I did notice the issue that both yourself and makaka-org are describing. If you run this sample within simulation, the behavior is not correct, and I am going to go ahead and file a bug on your behalf and make sure that this gets fixed.

2 Likes

Hello, makaka-org and mike-copley-unity. Finally, I got back to my own question. I went to the GitHub page and downloaded the repository. I opened the necessary scene, looked at the script (I didn’t really understand how it works yet), added several Debug.Logs to check, but again nothing worked. You can watch a short demo video at this link (I couldn’t upload it here)

https://drive.google.com/drive/folders/1zAOdDXYsHApx7HIWFqYz1i-UvCkOXXPw?usp=sharing

1 Like

In the video you run the scene and the camera looks at Simulated Tracked Image.

As is said above, this will not work.

I changed Simulation Environment as you said. The images are behind the camera. But when you start the application and point the camera at the images, they are still not recognized. Demo video at the link below (Demo01)

https://drive.google.com/drive/folders/1RxQZVTtP1amx9KH_rlT3s7FSrK-ML8kq?usp=drive_link

1 Like

Try to use Unity 2022.3 and AR Foundation 5 instead, which are recommended for production for Now.

Indeed, in earlier versions everything works fine, even better than I imagined (video Demo02 at the link below). I hope this will be fixed for Unity 6. Thank you for your help. Maybe later I will post the result for version 6, if I succeed :sweat_smile:

https://drive.google.com/drive/folders/1RxQZVTtP1amx9KH_rlT3s7FSrK-ML8kq?usp=sharing

Thank you for your help and patience :slightly_smiling_face:

1 Like

Thanks again to everyone for the advice. As a result, I downgraded AR Foundation to version 5.1.5, since the DynamicLibrary does not work correctly on version 6. I also realized that the simulation does not work quite correctly with images (in my case). I will argue: my task was to ensure that the images were not initially included in the project, in all examples (even when using the DynamicLibrary) the images are required in the Unity editor. Therefore, when comparing images from the simulation with those added during the application execution, the program considers that these are different images (different GUIDs, most likely something else does not match). However, if you make a build and run it on the phone, everything works fine. My biggest mistake was that I did not test the application on the phone at all stages of design, since I relied on the simulation mode. I hope this post will help someone :slightly_smiling_face:

1 Like