简述
本文档记录两种在Unity中使用ARCore实现图像识别的方式。
开发环境:
Unity 2021.3.29
依赖库:
    "dependencies":
    {
        "com.unity.xr.arfoundation": "4.1.5",
        "com.unity.xr.arcore": "4.1.5"
    }
方式1:
- 直接使用AR Foundation的2D图像跟踪API
方式2:
- 使用我基于AR Foundation实现的API
补充说明:这里基于AR Foundation实现的API,支持在AOT(Ahead-of-time)程序集和热更程序集中使用图像识别功能。
注意: 如需将现有项目从(已废弃)ARCore SDK for Unity 迁移到 Unity 的 AR Foundation 和(可选)ARCore Extensions,请参阅迁移指南。 如需从早期版本的 AR Foundation 升级现有项目,请参阅 Unity 的升级和迁移指南。
环境配置
1、在您的项目中,前往 Window > Package Manager。
2、选择Packages旁边的Unity Registry。
3、在搜索栏中输入“AR Foundation”,找到后点击Install。
4、在搜索栏中输入“ARCore XR plugin”,找到后点击Install。
5、前往 Edit > Project Settings。在 XR Plug-in Management 中,打开 Android 标签页并启用 ARCore。
6、配置Player Settings,详情如下:
| Player Settings > … | 值 | 
|---|---|
| Other Settings > Rendering | 取消选中 Auto Graphics API。 如果 Vulkan 列在 Graphics APIs 下,请将其移除,因为 ARCore 尚不支持 Vulkan。 | 
| Other Settings > Package Name | 使用 Java 软件包名称格式创建一个唯一的应用 ID。 例如,使用 com.example.helloAR。 | 
| Other Settings > Minimum API Level | 如果您要构建 AR 必备应用,请指定 Android 7.0 'Nougat' (API Level 24) or higher。 如果您要构建 AR 可选应用,请指定 Android API Level 19 or higher。 | 
| Other Settings > Scripting Backend | 选择 IL2CPP(而非 Mono)以允许在下一步中启用 ARM64 支持。在开发过程中:使用 Mono + 32 位 (ARMv7)安装 FAT(32 位 + 64 位)ARCore APK寄送到 Play 商店时:使用 IL2CPP同时启用 32 位 (ARMv7) 和 64 位 (ARM64),以满足 Play 商店的 64 位要求可选(在 2018.3 及更高版本中受支持):在 Build Settings 中,启用 Android App Bundles | 
| Other Settings > Target Architectures | 为了满足 Google Play 64 位要求,请启用 ARM64(64 位 ARM)。 使 ARMv7(32 位 ARM)保持启用状态,以支持 32 位设备。 | 
方式1:直接使用ARFoundation
官方文档
介绍了如何通过Unity的开发方式在应用中使用增强图像(图像识别)功能。
示例工程
在这个示例工程中 Assets/Scenes/ImageTracking/BasicImageTracking.unity演示了如何识别图片,并当识别到图片时,在图片位置加载一个prefab。
Assets/Scenes/ImageTracking/ImageTrackingWithMultiplePrefabs.unity演示了如何识别图片,并当识别不同图片时,在图片位置加载预设的与之对应的prefab。
关键类
ARTrackedImageManager
调用示例:
void OnEnable() => m_TrackedImageManager.trackedImagesChanged += OnChanged;
void OnDisable() => m_TrackedImageManager.trackedImagesChanged -= OnChanged;
void OnChanged(ARTrackedImagesChangedEventArgs eventArgs)
{
    foreach (var newImage in eventArgs.added)
    {
        // Handle added event
    }
    foreach (var updatedImage in eventArgs.updated)
    {
        // Handle updated event
    }
    foreach (var removedImage in eventArgs.removed)
    {
        // Handle removed event
    }
}
在运行时添加增强图像,示例如下:
[SerializeField]
ARTrackedImageManager m_TrackedImageManager;
void AddImage(Texture2D imageToAdd)
{
    if (!(ARSession.state == ARSessionState.SessionInitializing || ARSession.state == ARSessionState.SessionTracking))
        return; // Session state is invalid
    if (m_TrackedImageManager.referenceLibrary is MutableRuntimeReferenceImageLibrary mutableLibrary)
    {
        mutableLibrary.ScheduleAddImageWithValidationJob(
            imageToAdd,
            "my new image",
            0.5f /* 50 cm */);
    }
}
但是需要注意的是,在运行时添加增强图像的前提是,这之前存在一个ImageLibrary(可以是没有添加图像的)。
void AddImage(Texture2D imageToAdd)
{
    if (!(ARSession.state == ARSessionState.SessionInitializing || ARSession.state == ARSessionState.SessionTracking))
        return; // Session state is invalid
    var library = m_TrackedImageManager.CreateRuntimeLibrary();
    if (library is MutableRuntimeReferenceImageLibrary mutableLibrary)
    {
        mutableLibrary.ScheduleAddImageWithValidationJob(
            imageToAdd,
            "my new image",
            0.5f /* 50 cm */);
    }
}
编辑器操作
ImageLibrary定义
1、在Assets的目录下,右键->create->XR->Reference Image Library
2、在library中添加图像,如下图
绑定对象
1、在AR session Origin 下添加“ARTrackedImageManager”组件(也可在层级菜单右键创建),
2、在“ARTrackedImageManager”组件中绑定“ReferenceImageLibrary”
编写代码
以下是ARFoundation的示例代码
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.XR.ARSubsystems;
using UnityEngine.XR.ARFoundation;
namespace UnityEngine.XR.ARFoundation.Samples
{
    /// This component listens for images detected by the <c>XRImageTrackingSubsystem</c>
    /// and overlays some information as well as the source Texture2D on top of the
    /// detected image.
    /// </summary>
    [RequireComponent(typeof(ARTrackedImageManager))]
    public class TrackedImageInfoManager : MonoBehaviour
    {
        [SerializeField]
        [Tooltip("The camera to set on the world space UI canvas for each instantiated image info.")]
        Camera m_WorldSpaceCanvasCamera;
        /// <summary>
        /// The prefab has a world space UI canvas,
        /// which requires a camera to function properly.
        /// </summary>
        public Camera worldSpaceCanvasCamera
        {
            get { return m_WorldSpaceCanvasCamera; }
            set { m_WorldSpaceCanvasCamera = value; }
        }
        [SerializeField]
        [Tooltip("If an image is detected but no source texture can be found, this texture is used instead.")]
        Texture2D m_DefaultTexture;
        /// <summary>
        /// If an image is detected but no source texture can be found,
        /// this texture is used instead.
        /// </summary>
        public Texture2D defaultTexture
        {
            get { return m_DefaultTexture; }
            set { m_DefaultTexture = value; }
        }
        ARTrackedImageManager m_TrackedImageManager;
        void Awake()
        {
            m_TrackedImageManager = GetComponent<ARTrackedImageManager>();
        }
        void OnEnable()
        {
            m_TrackedImageManager.trackedImagesChanged += OnTrackedImagesChanged;
        }
        void OnDisable()
        {
            m_TrackedImageManager.trackedImagesChanged -= OnTrackedImagesChanged;
        }
        void UpdateInfo(ARTrackedImage trackedImage)
        {
            // Set canvas camera
            var canvas = trackedImage.GetComponentInChildren<Canvas>();
            canvas.worldCamera = worldSpaceCanvasCamera;
            // Update information about the tracked image
            var text = canvas.GetComponentInChildren<Text>();
            text.text = string.Format(
                "{0}\ntrackingState: {1}\nGUID: {2}\nReference size: {3} cm\nDetected size: {4} cm",
                trackedImage.referenceImage.name,
                trackedImage.trackingState,
                trackedImage.referenceImage.guid,
                trackedImage.referenceImage.size * 100f,
                trackedImage.size * 100f);
            var planeParentGo = trackedImage.transform.GetChild(0).gameObject;
            var planeGo = planeParentGo.transform.GetChild(0).gameObject;
            // Disable the visual plane if it is not being tracked
            if (trackedImage.trackingState != TrackingState.None)
            {
                planeGo.SetActive(true);
                // The image extents is only valid when the image is being tracked
                trackedImage.transform.localScale = new Vector3(trackedImage.size.x, 1f, trackedImage.size.y);
                // Set the texture
                var material = planeGo.GetComponentInChildren<MeshRenderer>().material;
                material.mainTexture = (trackedImage.referenceImage.texture == null) ? defaultTexture : trackedImage.referenceImage.texture;
            }
            else
            {
                planeGo.SetActive(false);
            }
        }
        void OnTrackedImagesChanged(ARTrackedImagesChangedEventArgs eventArgs)
        {
            foreach (var trackedImage in eventArgs.added)
            {
                // Give the initial image a reasonable default scale
                trackedImage.transform.localScale = new Vector3(0.01f, 1f, 0.01f);
                UpdateInfo(trackedImage);
            }
            foreach (var trackedImage in eventArgs.updated)
                UpdateInfo(trackedImage);
        }
    }
}
编译运行
BasicImageTracking场景:识别图片,在真实世界的图片位置加载图片和文字描述。
识别到图片后,运行效果如下:
ImageTrackingWithMultiplePrefab场景:识别不同图片,加载不同的Prefab
识别到图片后,运行效果如下:
方式2:间接使用ARFoundation(热更新版本)
使用说明
这里我对ARFoundation做了一层封装,简化了ARCore XR Plugin的使用流程,沿用之前集成其它MR SDK的通用接口。
注意事项
若不使用热更新,则可直接参考“方式1:直接使用ARFoundation的方式”实现即可。
主体程序
1、导入集成后的SDK 2、在场景中添加预制件 “ARCore Session” 3、在场景中添加预制件“HotfixDataLoader” 4、在“HotfixDataLoader”的DataDownLoader组件中添加服务器数据地址
至此,简单的主体程序已完成,编译打包即可。
编辑热更场景
目标
在热更场景中实现ARCore的图像识别
添加组件
在“ARCore Session”对象上添加组件“ARCoreImageDetect”,如下图所示。
在“ARCoreImageDetect”中的“Images”下所添加的就是用于识别的图像和识别到图像后加载的Prefab
在“ARCoreImageDetect”中的“DetectCallback”即是事件回调(见下节描述)。
事件回调
有时我们需要监听什么时候识别到图片,什么时候图片失去跟踪状态等信息。
可通过重写"DetectCallback"类
    public class DetectCallback : MonoBehaviour
    {
        public virtual void OnAdded(ARImageInfo image) { }
        public virtual void OnUpdate(ARImageInfo image) { }
        public virtual void OnRemoved(ARImageInfo image) { }
    }
示例如下:
    public override void OnUpdate(ARImageInfo image)
    {
        EqLog.i("DetectMethod", "image.name:" + image.name
            + ";image.position:" + image.transform.position);
    }
    public override void OnAdded(ARImageInfo image)
    {
        EqLog.i("DetectMethod", "image.name:" + image.name
            + ";image.position:" + image.transform.position);
        AndroidUtils.Toast("image.name:" + image.name
            + ";image.position:" + image.transform.position);
    }
    public void LoadCompleted()
    {
        AndroidUtils.Toast("图片数据库加载完成");
    }
场景导出
- 执行菜单栏“Holo-XR”->“BuildBundle-Android”,指定入口场景后,点击”导出“
- 场景打包的结果为zip包和version文件
热更运行
启动之前打包安装的主体程序。会自动校正数据版本,并下载最新场景数据。
运行效果如下:
