元宇宙的火爆带动了 VR、AR 等产品的热度。不管元宇宙是风口还是炒作,了解下 AR 技术总是没错的,目前大型的 AR 开发(游戏)主要是在移动设备上完成的,但是在一些新兴领域,比如电商、零售、美妆、广告等领域,WebAR 正在快速的与之结合并产生新的应用场景。本文介绍下 WebAR 的框架库:AR.js。主要讨论以下几个问题。
- AR 能做些什么,AR 背后依赖了哪些技术?
- AR.js 支持哪几种识别方式,每种方式的应用场景是什么?
- AR.js 项目结构是怎样的,我们到底应该引用哪个文件?
- AR.js 是如何识别到物体并追踪物体位置的?
AR.js 简介
AR.js 是一个用于实现 AR 的轻量级库。具有基于图像跟踪、位置跟踪、标记跟踪等功能。简单来讲,它能实现以下效果。
上图是 AR.js 官网的一张效果图,我们不妨想一想,在 Web 端实现这样的效果需要怎样的能力呢?首先我们需要在 Web 端唤起设备,比如手机的摄像头,用于拍摄周围的环境。然后在当前的环境中找到需要识别的标志,可能是一个标记,或是二维码,甚至是一个场景图。最后将虚拟的物体渲染到指定的标志上。为了达到以假乱真的效果,虚拟物体的位置和姿势需要和实际的场景一致,跟随镜头变化。可以简单的总结为:获取视频流能力 + 物体识别追踪能力 + 渲染图像/动画能力。
在 Web 上获取视频流并不是一件复杂的事情。对于目前在线音视频通讯领域的翘楚 WebRTC++ 来说,真的就是一行代码的事。相比较而言,物体的识别和追踪是很有难度的,这部分由 ARToolkit5 实现,ARToolkit5 是一个开源的 AR SDK。它是用 C/C++ 语言编写的库,使用电脑图像技术计算摄像头和标记之间的相对位置,从而使程序员能够将他们的虚拟对象覆盖到标记上面。当然,我们无法在 Web 端直接使用这个库,而是使用 JSARToolKit5,它是最新版本的 ARToolKit 增强现实库的 JavaScript 端口,为什么说是端口呢?因为这个 js 库是使用 wasm 技术执行 C/C++ 代码编译后的指令,核心还是 ARToolKit5。最后,渲染图像/动画能力由 A-Frame 或 three.js 提供,这是两个基于 WebGL 的 3D 渲染引擎。
综上可以看出,AR.js 并不是一个独立的技术,而是多种技术的结合。JSARToolKit5 负责获取视频流以及识别追踪物体,A-Frame 或 three.js 负责渲染虚拟物体。AR.js 做的最主要的事情就是将这两者连接起来,或者说是在渲染库的基础上增加 AR 的能力。从用户的角度看。A-Frame 框架增加了对 AR 的支持,three.js 也多了 AR 扩展。
AR 分类
除了渲染库的不同外,我们还需要了解 AR.js 支持的三种不同方式的展现方式,分别是标记跟踪,图像跟踪,基于位置。不同的展现方式表示不同的应用场景。这三种类型在示例图片中均有表现。
标记跟踪
标记跟踪方法需要一个事先制作好的标记(例如一个绘制着一定规格形状的模板卡片),然后把标记放到现实中的一个位置上,相当于确定了一个现实场景中的平面,然后通过摄像头对标记进行识别和姿态评估,并确定其位置,web 应用程序会在其顶部显示 3D 模型。标记的本质是一种简化的二维码。
AR.js 支持的标记分为三种。第一种就是 “Hiro”,这是 AR.js 默认标记,示例中被经常使用。实际区分效果不好,仅用于示例。
第二种是“条形码标记”,可以在这里查看标记完整列表。
最后一种,也是最通用的则是“自定义标记”。我们可以使用一些字母,符号来生成标记。AR.js 提供在线工具生成自定义标记。最终生成一个 “.patt” 文件。
使用自定义的标记对于标记的尺寸、形状、颜色都是有一定的要求的。不论我们上传的标记多么清晰,最终分辨率会被处理为 16 x 16 像素,因此作为标记的图片必须足够的清晰。标记必须是方形的,标记中的文本或是符号不能是旋转对称的,且它们不能包含颜色,因为生成 patt 文件的过程中会把颜色转为灰度,颜色没有意义。还有另一个重要方面是在标记的 “背景” 和周围环境之间最好有很高的对比度,比如黑色的背景是黑色,环境的背景最好是白色。这也是我们做标记的时候会将标记增加白色的边框的原因。
标记跟踪方式简单,稳定,计算速度也很快,但形状、颜色和尺寸有限制。一般用在非自然环境下,比如会印在传单,书籍,广告上等等。
图像跟踪
相较与标记跟踪,图像跟踪更符合我们想象中的 VR。我们使用图像作为标记,而不是二维码。当然,并不是所有的图像都适合用于跟踪。对于作为标记的图像,ArToolkit 会抽取其特征点建立图像描述,摄像机捕获的每一帧图像会被使用同样的方式进行解析,如果找到并且匹配图像,会计算出相对摄像头的图形标记位置和图案方向,最后将模型渲染到匹配到的帧画面的位置。
那什么样的图片才是合适的?。根据 AR.js 作者 Wiki 上的 Creating good markers 一文。以下三点是制作图像跟踪标记的关键。
- 视觉的复杂性
- 分辨率
- 待分辨物体
视觉的复杂性很好理解,一个图像的视觉复杂性取决于它所拥有的信息量。我们不能奢求从一张白色图片中获得更多的信息,但如果这张白色图片被填充了各种物体,我们就能得到很多的信息点,比如一个复杂的曲线,一个突兀的直角等等,这些点是独特的并且容易识别,这些点被称为特征点。因此,当试图生成一个标记时,复杂的图像往往存在更多的特征点,这些特征点对后续的追踪至关重要。
创建标记时最重要的分辨率,个人认为比视觉复杂性更加重要。分辨率定义了元素的详细程度。如果一张图片的宽度为4000,高度为2104,那它就有 8416000 像素。800 万像素是很多像素,足以显示细节和清晰的小元素,而较小的图像没有足够的像素,很难提取出足够多的特征点。
最后一点不是针对图像标记的,而是说我们使用摄像头对准待识别物体的时候,相机和标记之间的距离将影响用户体验,这一点不必多说,我们需要一个适合的距离来识别物体。
这三点总的来看是非常符合直觉的,使用包含大量信息且具有高分辨率的图像,识别的时候注意一定的距离,保证清晰度,就能得到正确的结果。下图是作者给出的一个示例。
经过特征点提取后,我们最终会得到三个文件作为图像描述符。后缀分别是 fset,fset3,iset。它们中的每一个在文件扩展名之前都有相同的前缀。暂且不管这是哪个文件中的内容是什么,我们只要知道,这三个文件包含了图片全部的特征,可以完全的代表这张图片。
基于位置的 AR
这种 AR 基于真实世界的位置来在用户设备上显示虚拟影像的。我们可以认为 AR 影像被 “固定” 到了真实位置。用户可以通过智能手机看到现实世界中的 AR 内容。移动和旋转手机将使 AR 内容根据用户的位置和旋转而改变,并根据它们与用户的距离显得更大/小。当然,使用之前先确保手机的 GPS 处于开启状态。
基于位置的 AR 在户外互动的时候很有效,比如建立旅游路线指南、城市内部导航、寻找名胜古迹,如建筑、博物馆、餐馆、酒店等。也可以用在户外游戏,如寻宝探险收集类游戏等等。
AR.js 项目结构
AR.js 项目结构与它的渲染框架和 AR 分类是一致的。
AR.js 有两种不同的版本:aframe 和 threejs。这两个版本互不干预,没有耦合。从 AR.js 项目的一级目录也可以看出,他们是独立运作的。我们使用 AR.js 时,导入的脚本取决于所需的功能以及要使用的渲染库 (A-Frame 或 three.js)。
aframe
- build
- aframe-ar-location-only.js
- aframe-ar-nft.js
- aframe-ar.js
- examples
- src
data // examples 用到的数据
test // 测试文件
three.js
- build
- ar-nft.js
- ar.js
- examples
- src
- vendor
aframe 和 three.js 目录下的结构是相似的。src 目录存放源码相关;examples 是示例;build 是打包后的产物,也就是我们使用时引用的 js。three.js 的产物有两个,ar.js 和 ar-nft.js,分别用于标记跟踪和图像跟踪。aframe 产物有三个,分别是 aframe-ar-location-only.js,aframe-ar-nft.js 和 aframe-ar.js。aframe-ar.js 用于 标记跟踪,aframe-ar-nft.js 用于图像跟踪。aframe-ar-location-only.js 未在示例中看到单独使用,看打包命令是把 gps 相关的代码封装为实体,我们一般也不会单独使用。
单独说明下,这里的 nft 后缀不是指 NFT 资产,而是 Natural Feature Tracking,自然特征点追踪,所以带 nft 后缀结尾的文件,就是图像跟踪需要引入的 js。还有一点需要注意的是,基于位置的 AR 仅在 aframe 框架中实现,如果我们要基于地理位置做 AR 应用,需要使用 Aframe 框架并引入 aframe-ar-nft.js 才可以。
基于灰度的图像识别
AR 应用中,我们需要在摄像机中识别出我们预先准备的标记或者图像,然后将虚拟物体展示在它上面。这是一个图像识别的过程。对于标记跟踪类型的 AR,由于标记简单,且易于识别,它的识别过程比较容易理解。
将标记进行灰度处理后。生成一个大小为 16 * 16,且值范围在 0 -255 之间的矩阵,0 是黑色,255 是白色。将这个矩阵放在一个后缀为 patt 的文件中,作为识别的依据。实际上的 patt 文件是由多个 16 * 16 矩阵拼接的,内容基本一致,只是对矩阵做了旋转。理解的时候可以认为就是生成了如下的一个文件。
255 255 255 255 255 255 255 255 254 255 255 255 255 255 255 255
255 255 255 255 255 255 255 231 16 255 255 255 255 255 255 255
255 255 255 255 255 255 255 0 0 255 255 255 255 255 255 255
255 255 255 255 255 255 255 0 0 100 255 255 255 255 255 255
255 255 255 255 255 255 28 0 0 0 255 255 255 255 255 255
255 255 255 255 255 255 0 0 0 0 221 255 255 255 255 255
255 255 255 255 255 72 180 0 0 0 1 255 255 255 255 255
255 255 255 255 255 0 255 225 0 0 0 254 255 255 255 255
255 255 255 255 165 89 255 255 0 0 0 12 255 255 255 255
255 255 255 255 0 255 255 255 121 0 0 0 255 255 255 255
255 255 255 246 0 0 0 0 0 0 0 0 65 255 255 255
255 255 255 6 255 255 255 255 255 53 0 0 0 255 255 255
255 255 252 0 255 255 255 255 255 255 0 0 0 186 255 255
255 255 0 126 255 255 255 255 255 255 2 0 0 0 255 255
251 5 0 0 108 252 255 255 254 184 0 0 0 0 5 239
255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
这个就是待识别标记的灰度图,相机将拍摄到的画面转换为一帧一帧的图片,然后在图片中检测这个标记。识别的大概过程是先将图片进行灰度处理,然后提取轮廓、模板匹配。这种匹配方式简单,处理起来速度很快。
基于特征点的识别
复杂的图像使用灰度来识别的正确率太低,此时如何来进行图像的识别呢?
首先会将模板图片进行特征检测,生成特征描述符。对于当前场景适用同样的方式进行检测,如果检测到的特征点和模板特征点匹配数量超过阈值,认为扫描到了模板图像。具体来讲的话,ARToolKit5 中图像识别使用的是 FREAK 算法(旧版本使用 SURF 算法)。FREAK 算法包括特征点提取,生成 FREAK 二进制描述符,进行特征匹配三部分。简单的讲下个人的理解。
一个图像的特征可以分为全局特征以及局部特征,全局特征包括颜色特征、纹理特征、形状特征,这些特征容易受到环境的干扰,光照,旋转,噪声等不利因素都会影响全局特征。局部特征点是图像特征的局部表达,往往对应着图像中的一些线条交叉,边缘、斑点、角点等,它们都具有很好的稳定性,不容易受外界环境的干扰。因此,图像特征提取提取的就是局部特征。具体来说,局部特征一般指的是斑点和角点。斑点通常是指与周围有着颜色和灰度差别的区域,如街上的一辆车。墙上的一个钟,它是一个区域。而角点则是图像中一边物体的拐角或者线条之间的交叉部分。检测的算法有很多,比如 SIFT、SURF 算法就是经典的特征点检测方法,FAST 算法是经典的检测角点的方法。
FREAK 算法是用 FAST 算法来进行特征点提取的。FAST 的作者将 FAST 角点定义为:若某像素与其周围邻域内足够多的像素点相差较大,则该像素可能是角点。根据这个原理,我们只要计算某个像素点与周围像素点的像素差即可,当然,实际上算法做的要比这个复杂。为了让不同尺寸下图像可以被识别,需要建立尺度空间,在尺度空间进行特征点检测。我们可以认为最终特征点是在图像多个尺度下的特征,也就是说,我们得到了这个图像从模糊到清晰的全部特征点。
特征点只是一个点,并不能表示什么。特征点及其周围的点组成的形状才是图像的局部特征。因此有了特征点后,我们需要对特征点周围进行采样,FREAK 算法模拟人眼的视觉系统,距离特征点越近,采样点越密集。得到这些采样点后,两两比较灰度值,大表示为 1,小表示为 0,最终得到的一串二进制的文本,就是这个特征点的二进制描述符。后续还会对多个描述符进行过滤(降维)和排序(高方差排前面)分组。
最后是特征匹配,FREAK 描述符是高方差 ——> 低方差的排列,而高方差表征了特征明显的信息,一般是模糊信息,而低方差表征了细节信息。匹配的时候算法模拟人“扫视”行为,先比较前若干位数据,若两个待匹配的特征点欧式距离小于设定的阈值,则再用剩余的位信息进行匹配。这样匹配起来速度很快。
姿态估计
想要正确的渲染虚拟物体,我们需要知道相机的姿态和物体的姿态。相机的姿态可以通过陀螺仪等硬件设施获得,物体的姿态需要根据标记的姿态进行估计。
我们在上一步进行物体识别的时候,获得了标记图片和当前场景的大量特征点,我们称其为“点云”,根据标记图片的点云和当前场景的点云,计算出的两片点云的变换矩阵(平移 + 旋转),就是物体的姿态变换矩阵。
计算这个矩阵使用的方法称为 ICP,全称是 Iterative Closest Point,即迭代最近点。整个过程时候这样的。
- 从两组点云中选择匹配的点对,形成匹配两组点云。匹配关系在物体识别的时候已经确定。
- 先对平移向量进行初始的估算,具体方法是分别得到点云的中心,其差值就是平移矩阵。
- 点云中的每个点都减去所在点云的中心坐标,然后计算旋转矩阵 R,这是一个最优化问题,能得到一个当前条件下最优旋转矩阵。这一步采用什么样的方法来使其收敛到最小,也是一个比较重要的问题,一般使用基于奇异值分解的方法。
- 判断点云经过平移和旋转后的距离阈值是否满足结束条件,若不满足,再次进行迭代,直到迭代次数达到阈值或者误差小于阈值。
总结
AR.js 并不是一个独立的技术,而是多种技术的结合,包括用于获得视屏流的 WebRTC,用于识别物体和计算位置的算法库 ARToolkit5,还有用于渲染的 A-Frame 或 three.js。
AR.js 支持标记跟踪,图像跟踪和基于位置的 AR。标记跟踪方法需要一个事先制作好的标记,该标记必须是方形的,会被解析为 16 x 16 像素的灰度图。图像跟踪方式是抽取图像中的特征点进行识别,待识别的图像分辨率要高,且拥有的信息量要大。基于位置的 AR 是依据 GPS 给出的经纬度进行判断,显示虚拟物体。
AR.js 项目结构与它的渲染框架和 AR 分类是一致的。从大的层面看,分为 aframe 和 threejs 两个版本。每个版本又分为带 nft 后缀以及不带 nft 后缀两类,nft 表示 Natural Feature Tracking,也就是图像跟踪,因此带 nft 后缀结尾的文件,就是图像跟踪需要引入的 js。还有一点需要注意的是,基于位置的 AR 仅在 aframe 框架中实现。
标记跟踪类型的 AR 使用的是基于灰度的图像识别,图片进行灰度处理,然后提取轮廓、模板匹配。这种匹配方式简单,处理起来速度很快。图像跟踪类型的 AR 使用的是基于特征点的图像识别,FAST 算法检测特征点,生成 FREAK 描述符,然后做匹配。完成匹配后,每一帧都要使用 ICP 的方式计算物体的姿态。
总结下 ARToolkit 进行物体识别和姿态估计的流程:摄像机捕获真实世界的视频,并将视屏转换为一帧帧的图像,并根据我们预先指定的方式,在其中搜索模板标记。如果找到并且匹配到标记后,计算出相对摄像头的图形标记位置和图案方向。还要利用摄像头位姿来调整模型的位置和方向,最后将模型渲染到标记所在的位置。
元宇宙的火爆带动了 VR、AR 等产品的热度。不管元宇宙是风口还是炒作,了解下 AR 技术总是没错的,目前大型的 AR 开发(游戏)主要是在移动设备上完成的,但是在一些新兴领域,比如电商、零售、美妆、广告等领域,WebAR 正在快速的与之结合并产生新的应用场景…