简述
本文档记录两种在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文件
热更运行
启动之前打包安装的主体程序。会自动校正数据版本,并下载最新场景数据。
运行效果如下: