写在前面的话
本系列笔记旨在记录作者在学习Unity中的AR开发过程中需要记录的问题和知识点。主要目的是为了加深记忆。其中难免出现纰漏,更多详细内容请阅读原文以及官方文档。
汪老师博客
文章目录
- 2D图像检测
- 创建一个图像检测工程
- 图像追踪的禁用和启用
- 多图像追踪
- 运行时参考图像库
- 运行时添加参考图
2D图像检测
在ARFoundation中,图像跟踪系统依据参考图像库中的图像信息尝试在周围环境中检测到匹配的2D图像并跟踪。在接下来的学习中我们将会提到一些术语:
-
Reference Image(参考图像),识别2D图像的过程中,实际上是一个特征值对比的过程。AR Foundation将从摄像头中获取的图像信息与参考图像库的图像特征值信息进行对比,而存储在参考图像库中的用于对比的图像就是Reference Image。
一旦对比成功,真实环境中的图像就会与参考图像库的参考图像建立对应关系,并且同时检测真实2D图像的姿态信息。 -
Reference Image Library(参考图像库),参考图像库存储了用于对比的参考图像,每一个图像跟踪程序都必须有一个参考图像库。
在参考图像库中存储的实际是参考图像的特征值信息而不是原始图像,这有助于提高对比速度和增强检测系统的鲁棒性(耐操性,即在异常下依旧运行的能力)(想象一下如果用原始图像进行检测,那么则需要在像素空间上对图像进行对比,对像素的实时检测太消耗资源了!因此如果将图像映射到特征空间使用特征值检测将会方便很多!小到图像检测,大到人工智能都运用了这个原理!)。
参考图像库越大,图像对比就越慢,因此参考图像库的图像不要超过1000张。 -
Provider(算法提供方),由于AR Foundation是基于底层SDK的图像追踪的API之上的,因此它只提供一个图像检测的接口,具体的图像识别算法由Provider提供。
创建一个图像检测工程
在ARFoundation中,图像跟踪的操作分为两步:
- 建立一个参考图像库
- 建立一个AR Tracked Image Manager组件,并将需要实例化的Prefab挂载在Tracked Image Prefab上
按上述步骤,在Unity中新建一个工程,第一步建立一个参考图像库,首先在Project窗口中的ImageLib文件夹下点击鼠标右键并依次选择Create->XR->Reference Image Library新建一个参考图像库,并命名为RefImageLib,如图所示。
选择新建的RefImageLib参考图像库,为其添加一些参考图像
属性 | 描述 |
---|---|
Name | 一个标识参考图像的名字,这个名字在做图像对比时没有作用,但在比对匹配成功后我们可以通过参考图像名字获知是哪个参考图像,参考图像名字是可以重复的,因为在跟踪时,跟踪系统还会给每一个参考图像一个referenceImage.guid值,这个值是唯一的。 |
Specify Size | 这是个可选值,为加速图像检测识别过程,一些底层SDK要求提供一个2D待检测图像的物理尺寸,所以如果要设置,这个值一定会是一个大于0的长宽值对,当一个值发生变化时,Unity会根据参考图像的比例自动调整另一个值。 |
Keep Texture at Runtime | 一个默认的纹理,这个纹理可以用于修改Prefab的外观。 |
为了实现图像追踪,我们需要选择挂载Trackable的对应Manager在AR Session Origin上,这里我们选择AR Tracked Image Manager。挂载参考图像库和生成预制体,并设定最大可跟踪的动态图像数。
注意:ARFoundation中的图像跟踪是一个非常消耗CPU性能的任务,因此不要设定过多的动态图像追踪数量。
此外,动态图像追踪和底层SDK的算法有很大关系,因此使用时应当阅读对应平台的SDK资料。ARFoundation目前也不支持动态添加参考图像。
(由于SDK的生成设置,生成的模型方向反了,这只能在模型内部调整朝向来解决)
图像追踪的禁用和启用
void SetAllImagesActive(bool value)
{
foreach (var img in mARTrackedImageManager.trackables)
img.gameObject.SetActive(value);
}
与平面检测开启和关闭类似,图像追踪的禁用和启用也相当简单,只需对Trackable的gameObject进行SetActive即可。
多图像追踪
在上图中不难发现,AR Tracked Image Manager中能够生成的预制体只有一个,如果我们想要实现多图像追踪,并生成多个预制体该如何实现呢?
默认的,AR foundation可以对同一个图像进行多个虚拟对象的实例化,但是只能实例化我们在Manager上挂载的那一类Prefab,也就是只能对一类图像进行多个对象的实例化,例如只识别SPIDER的图像或者CAT的图像,而不能同时识别二者。然而实际应用中我们更希望对多类图像都进行识别和实例化,如何能够实现?
挂载一个新的Tracked Image Manager?这个方案是不行的,因为同类的Manager在AR程序中只会实例化一个(单例模式),这是为了防止多个同类Manager同时存在造成追踪混乱。
如果不能实例化多类虚拟对象,这将极大的限制跟踪图像的实际应用,所以为了实例化多类虚拟对象,我们只能动态的修改Tracked Image Prefab属性。
经过测试,我们发现在ARFoundation中,AR Tracked Image Manager组件在trackedImagesChanged事件触发之前就已经实例化了虚拟对象,因此如果在捕获到第一个需要实例化Prefab的图像时,动态的替换Manager中的Prefab为下一个需要实例化的Prefab即可。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
public class MultiImageTracking : MonoBehaviour
{
ARTrackedImageManager ImgTrackedmanager;
public GameObject[] ObjectPrefabs;
private void Awake()
{
ImgTrackedmanager = GetComponent<ARTrackedImageManager>();
}
private void OnEnable()
{
ImgTrackedmanager.trackedImagesChanged += OnTrackedImagesChanged;
}
void OnDisable()
{
ImgTrackedmanager.trackedImagesChanged -= OnTrackedImagesChanged;
}
void OnTrackedImagesChanged(ARTrackedImagesChangedEventArgs eventArgs)
{
foreach (var trackedImage in eventArgs.added)
{
OnImagesChanged(trackedImage.referenceImage.name);
}
// foreach (var trackedImage in eventArgs.updated)
// {
// OnImagesChanged(trackedImage.referenceImage.name);
// }
}
private void OnImagesChanged(string referenceImageName)
{
if (referenceImageName == "Spider")
{
ImgTrackedmanager.trackedImagePrefab = ObjectPrefabs[1];
Debug.Log("Tracked Name is .." + referenceImageName);
Debug.Log("Prefab Name is .." + ImgTrackedmanager.trackedImagePrefab.name);
}
if (referenceImageName == "Cat")
{
ImgTrackedmanager.trackedImagePrefab = ObjectPrefabs[0];
}
}
}
上述代码中,我们提前准备好了一个预制件的数组,并在Image Tracked Manager可用时为其注册了一个事件,一旦可追踪图像产生变化,我们就设定可追踪图像为下一个目标,并同时更新预制件。但这个方法也有弊端,那就是我们对预制件的生成只能顺序地执行。
因此改变思路,我们需要在随机检测到参考图像时生成对应的预制件,因此则需要动态地替换当前需要产生的预制件的目标:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
public class MultiImageTracking : MonoBehaviour
{
ARTrackedImageManager ImgTrackedManager;
private Dictionary<string, GameObject> mPrefabs = new Dictionary<string, GameObject>();
private void Awake()
{
ImgTrackedManager = GetComponent<ARTrackedImageManager>();
}
void Start()
{
mPrefabs.Add("Cat", Resources.Load("Cat") as GameObject);
mPrefabs.Add("Spider", Resources.Load("Spider") as GameObject);
}
private void OnEnable()
{
ImgTrackedManager.trackedImagesChanged += OnTrackedImagesChanged;
}
void OnDisable()
{
ImgTrackedManager.trackedImagesChanged -= OnTrackedImagesChanged;
}
void OnTrackedImagesChanged(ARTrackedImagesChangedEventArgs eventArgs)
{
foreach (var trackedImage in eventArgs.added)
{
OnImagesChanged(trackedImage);
}
// foreach (var trackedImage in eventArgs.updated)
// {
// OnImagesChanged(trackedImage.referenceImage.name);
// }
}
private void OnImagesChanged(ARTrackedImage referenceImage)
{
Debug.Log("Image name:"+ referenceImage.referenceImage.name);
Instantiate(mPrefabs[referenceImage.referenceImage.name], referenceImage.transform);
}
}
在上述代码中,我们用字典提前存储了对应模型的键值对。如果图像信息变化,检测所有当前追踪的图像,并在当检测到参考图像名称的模型时就生成对应的预制件。
为了实现代码所描述功能,我们还要完成两项工作,第一项工作是将Prefabs放到Resources文件夹中方便动态加载,第二项工作是保证mPrefabs中的key与RefImageLib参考图像库中的参考图像名一致。至此,我们已实现自由的多图像多模型功能。
运行时参考图像库
在前文中使用参考图像库的时候,我们提前为库编辑好了检测的参考图内容。但有时我们想要动态的创建参考图像库。由于AR Tracked Image Manager
在启动时其参考图像库不能为null值,我们可以使用下列代码动态添加AR Tracked Image Manager
和参考图像库。
trackImageManager = gameObject.AddComponent<ARTrackedImageManager>();
trackImageManager.referenceLibrary = trackImageManager.CreateRuntimeLibrary(runtimeImageLibrary);
trackImageManager.maxNumberOfMovingImages = 2;
trackImageManager.trackedImagePrefab = prefabOnTrack;
trackImageManager.enabled = true;
参考图像库可以是XRReferenceImageLibrary
或者RuntimeReferenceImageLibrary
类型,XRReferenceImageLibrary
可以在Editor编辑器中创建,但不能在运行时修改,不过在运行时XRReferenceImageLibrary
会自动转换成RuntimeReferenceImageLibrary
。(XR参考图像库就是我们之前在创建时使用的图像库类型,在运行时该类型会自动转换为Runtime参考图像库)
使用下列代码将XR参考图像库转为Runtime参考图像库
XRReferenceImageLibrary serializedLibrary = ...
RuntimeReferenceImageLibrary runtimeLibrary = trackedImageManager.CreateRuntimeLibrary(serializedLibrary);
使用提前准备好的XR图像参考库也可以在运行时来动态转换:
[SerializeField]
XRReferenceImageLibrary[] mReferenceImageLibrary;
private int currentSelectedLibrary = 0;
private ARTrackedImageManager trackImageManager;
public void SetReferenceImageLibrary(int selectedLibrary = 0)
{
trackImageManager.referenceLibrary = trackImageManager.CreateRuntimeLibrary(mReferenceImageLibrary[selectedLibrary]);
mLog.text = System.String.Format("切换参考图像库{0}成功!",selectedLibrary);
}
运行时添加参考图
一些底层SDK支持在运行时动态添加新的参考图像到参考图像库,目前ARCore与ARKit都支持在运行时添加参考图像。要检测是否支持添加参考图(或者任意某功能),我们可以对描述符进行检测:
if (trackedImageManager.descriptor.supportsMutableLibrary)
如果SDK支持在运行时动态添加参考图,那么使用该功能时需要将RuntimeReferenceImageLibrary
转为MutableReferenceImageLibrary
再使用,如果需要在运行时创建全新的参考图像库,也可以使用无参的CreateRuntimeLibrary()
方法,然后将其转换为MutableRuntimeReferenceImageLibrary
使用:
var library = trackedImageManager.CreateRuntimeLibrary();
if (library is MutableRuntimeReferenceImageLibrary mutableLibrary)
{
// 添加图像到参考图像库
}
在运行时动态添加参考图像是一个计算密集型任务,因为需要提取参考图像的特征值信息,这大概需要花费几十毫秒时间,因此需要很多帧才能完成添加,为防止同步操作造成应用卡顿,可以利用Unity Job系统(一个安全的无锁多线程系统)异步处理这种操作。
使用Job定义一个添加图像的异步方法:
public static JobHandle ScheduleAddImageJob(this MutableRuntimeReferenceImageLibrary library, Texture2D texture,
string name, float? widthInMeters,JobHandle inputDeps = null)
使用JobHandle
作为返回值,我们可以检查任务是否完成。通常再使用完毕后应当销毁JobHandle
以避免性能开销。我们可以使用该方法同时添加多个参考图像到参考图像库中,即使当前的参考图像库正在追踪图像或是处于使用中也没关系。
动态添加参考图像对图像有一些特定要求。
第一要求图像可读写,因为添加图像时需要提取图像特征值信息;
第二要求图像格式必须为应用平台上支持的格式,通常选择RGB 24bit或者RGBA 32bit格式。这个需要在图像的import setting中设置,如图所示:
经过测试,动态添加参考图像对图像编码格式也有要求,通常只支持JPG、PNG格式
Hint
如果作为动态添加的参考图像没有启用Read/Write Enable,编译时将提示The texture must be readable to be used as the source for a reference image;
如果所选定的格式平台不支持,编译时将提示The texture format ETC_RGB4 is not supported by the current image tracking subsystem。
1. [SerializeField]
2. private Text mlog;
3. [SerializeField]
4. private Button addImageButton;
5.
6. [SerializeField]
7. private GameObject prefabOnTrack;
8.
9. private Vector3 scaleFactor = new Vector3(0.2f, 0.2f, 0.1f);
10.
11. private XRReferenceImageLibrary runtimeImageLibrary;
12.
13. private ARTrackedImageManager trackImageManager;
14.
15. [SerializeField]
16. private Texture2D AddedImage;
17.
18. void Start()
19. {
20. trackImageManager = gameObject.AddComponent<ARTrackedImageManager>();
21. trackImageManager.referenceLibrary = trackImageManager.CreateRuntimeLibrary
(runtimeImageLibrary);
22. trackImageManager.maxNumberOfMovingImages = 2;
23. trackImageManager.trackedImagePrefab = prefabOnTrack;
24. trackImageManager.enabled = true;
25. trackImageManager.trackedImagesChanged += OnTrackedImagesChanged;
26. addImageButton.onClick.AddListener(() => StartCoroutine(AddImageJob(AddedImage)));
27. }
28.
29. void OnDisable()
30. {
31. trackImageManager.trackedImagesChanged -= OnTrackedImagesChanged;
32. }
33.
34. public IEnumerator AddImageJob(Texture2D texture2D)
35. {
36. yield return null;
37.
38. try
39. {
40.
41. MutableRuntimeReferenceImageLibrary mutableRuntimeReferenceImageLibrary =
trackImageManager.referenceLibrary as MutableRuntimeReferenceImageLibrary;
42. var jobHandle = mutableRuntimeReferenceImageLibrary.ScheduleAddImageJob
(texture2D, "Spider", scaleFactor.x);
43.
44. while (!jobHandle.IsCompleted)
45. {
46. new WaitForSeconds(0.5f);
47. }
48. mlog.text = "添加图像成功!";
49. }
50. catch (System.Exception e)
51. {
52. mlog.text = e.Message;
53. }
54. }
55.
56. void OnTrackedImagesChanged(ARTrackedImagesChangedEventArgs eventArgs)
57. {
58. foreach (ARTrackedImage trackedImage in eventArgs.added)
59. {
60. trackedImage.transform.Rotate(Vector3.up, 180);
61. }
62.
63. foreach (ARTrackedImage trackedImage in eventArgs.updated)
64. {
65. trackedImage.transform.Rotate(Vector3.up, 180);
66. }
67. }
在上述代码中,当检测图像改变的事件触发,就会对追踪图像进行旋转。在点击按钮后开启一个协程,在其中开辟一个Job线程为MutableRuntimeReferenceImageLibrary
动态的添加参考图。使用jobHandle.IsCompleted
来检测该Job的成功状态。
这里需要注意的是,我们使用了Unity Job系统,目前该系统已作为一个Package单独列出来了,因此需要使用Package Manager添加Burst包。Burst包依赖于NDK,所以还需要正确设置Unity中的NDK路径,如Unity 2019.3.0.6b需要NDK r19包。