vision kit 平面检测从入门到放弃

AR 增强现实,是指在真实世界的基础上,通过计算机生成的虚拟信息,将虚拟信息与真实世界进行融合,从而达到增强现实的效果。

核心技术及相关技术栈

想要实现 WebAR 效果,四个步骤:读取、识别、跟踪、渲染。
拿扫福举 ?:
打开相机画面(读取)、扫到福字(识别)、渲染虚拟场景比如小兔子跳出来的动画(渲染)

0.png
技术上说,粗略流程:

1.png

技术栈:

5.png

? 读取

  • 浏览器中:WebRTC.js,最关键的 API 方法是 getUserMedia() ,实时获取摄像头的视频流

  • 微信小程序中:腾讯视频云的 liteavsdk

微信官方总结两者区别

? 识别与跟踪

浏览器中可用的库 : JSARToolKit、Tracking.js、jsFeat、js-aruco

  • JSARToolKit(1999 年发布,一直更新至今) 和 js-aruco 都是基于特定的标记去跟踪的,而不能像 Tracking.js 那样进行特征检测。它们依赖于事先印刷好的标记,通过检测标记的位置和方向,从而定位和跟踪物体。在使用这些库时,需要将标记嵌入到需要跟踪的物体上,因此它们更适用于需要定向和固定跟踪的应用场景。
  • Tracking.js 和 jsFeat 等库则使用计算机视觉算法进行特征检测和跟踪,可以在图像中检测和跟踪目标对象,而不需要特定的标记。这些库更适用于需要动态跟踪的场景,例如手势识别、人体识别等应用。不过对于 web 端来说,算力可能跟不上。

微信小程序中可用的库:

  • Vision Kit 是一个使用一些深度学习框架 (如 TensorFlow) 进行深度学习的训练和推断,结合一些计算机视觉算法(如 OpenCV),形成的识别追踪库。具备:平面检测、人脸识别、手势识别、跟踪等能力。

    AR 中最难部分就是跟踪

?️ 渲染

浏览器中可用的库:Three.js、Babylon.js、playCanvas、pixi.js(2d)

  • Three.js 是最早出现的 WebGL 库之一,提供了丰富的 3D 渲染和动画功能,支持多种 3D 模型格式和材质。
  • Babylon.js 是由微软开发的 3D 游戏引擎,也提供了类似的 3D 渲染和动画功能,并支持物理引擎和多人游戏等高级特性。
  • playCanvas 则是一款基于 WebGL 的在线游戏开发平台,提供了完整的游戏开发工具和游戏发布服务,可以方便地在 Web 浏览器中创建和发布高品质的 3D 游戏。

微信小程序中可用的库,几乎都是 three 的微信小程序定制版:

? 技术方案

在 web 端(包括小程序)实现完整的 AR 能力,有哪些技术方案呢?

方案 and 能力 特定图像识别 特征检测 性能
AR.js(WebRTC+JSARToolKit+Three.js/Babylon.js/A-Frame) ✔️ × ✔️
WebRTC + trackingJS + ThreeJS ✔️ ✔️ ×
video 标签 + ThreeJS + 陀螺仪 × × ✔️
微信小程序 vision kit + threejs-miniprogram ✔️ ✔️ ✔️
其他小程序

项目实践

关键点:① 识别现实世界中的平面,将一个 3D 人物模型“放置”在平面上。 ②3D 经灯光照射有阴影效果,③ 对 3D 模型可进行简单的平移、旋转、缩放操作以便于完成虚拟人物与现实中的人合影。
因为需要 “识别现实世界中的水平面”,也就是说需要特征检测,所以最(wu??‍♀️)后(nai?‍♂️)选择了微信小程序,采用 vision kit + threejs-miniprogram 的技术方案。

微信小程序中的 AR 平面检测能力

vision kit 有 v1 v2 两个版本,高版本不一定更好哦,下面先介绍一下差异性。
2.png

⭐️ API(能力)

v2 肯定比 v1 多喽~

  • v1 调用 hitTest 进行平面检测。
  • v2 除了 hitTest,还增加了实时监听平面的功能,事件有:’addAnchors’、’updateAnchors’、’removeAnchors’
  • v1 只能检测水平面
  • v2 除了水平面,也可以识别竖平面

⚖ 稳定性

v1 版本不如 v2 版本稳定。

  • v1 版本有动态物体从镜头前经过就会把 3D 模型带走,v2 版本不会。
  • 但当镜头晃动速率较高的时候,不论哪个版本,模型还是会漂移。

以上是实践总结,以下是官方说法
V1 版,当手机摄像头没有朝向地面时,3D 模型会漂移、忽大忽小、无法停留在开始的位置。原因是 AR 以地面为跟踪目标,如果地面从手机画面中消失,V1 版 AR 就无法正常运行。
V2 版,AR 以房间环境为跟踪目标,不会因为手机姿态造成 3D 模型漂移。但遮住手机摄像头,V2 版 AR 也无法正常运行。

✌? 准确性与成功率

v2 检测准确性高于 v1,但成功率低于 v1,具体难度取决于手机。
通过项目实践的经验,个人认为原因是:

  • v2 是真的检测现实中的平面,如果对准的位置是墙,那一定是检测不到的(准确高),但有些手机对准地面可能需要手机放平才检测到(难度高)。
  • v1 之所以容易,是因为 v1 并没有识别现实场景,而是直接去生成一个水平面。所以 v1 几乎每次都成功(难度低),但手机不对着地面的时候,模型会悬空(准确低)。

? 兼容性

开发期间调研,iphone 8 以上均支持 v2 版本,Android 很少部分支持 v2。(项目线上数据待统计,不知道能否公开)

官方数据:
对微信版本要求、不同手机支持程度

世界坐标系原点的变化

  • v1 以识别成功的平面中心点,为世界坐标系原点。也就是说每次 hitTest 世界坐标的原点都更新了。
  • v2 以手机相机的开始位置,为世界坐标系原点。也就是说 createVkSession 运行成功的瞬间,出现相机画面,屏幕中心点为世界坐标原点。后面 hitTest 再也不变了。

代码流程

针对官方代码流程画个图吧,我们的项目相比较官方示例,在模型效果和交互上有一些提升。AR 部分的核心代码一样的。

3.png

难点 or 坑点

(下面的代码都视作伪代码)
scene – 场景;renderer – 渲染器;modelGroup – 模型和模型阴影; light – 灯光;

?模型效果(阴影等)

  • 阴影对象
// 获取模型的边界框
const boundingBox = new THREE.Box3().setFromObject(model);
// 定义平面的尺寸和中心点
const planeWidth = 3;
const planeHeight = 3;

// 计算平面的位置
const planePosition = new THREE.Vector3(
  (boundingBox.max.x + boundingBox.min.x) / 2,
  -0.05, //y的位置固定取值,前提是与模型设计师沟通好,人脚低的位置在y轴0点
  (boundingBox.max.z + boundingBox.min.z) / 2
);

// 计算平面的旋转角度(与地面平行)
const planeRotation = new THREE.Euler(-Math.PI / 2, 0, 0);

// 创建了一个半径为1、分成32个段的圆形
const geometry = new THREE.CircleBufferGeometry(1, 32);
const material = new THREE.MeshStandardMaterial({
  color: 0xffffff,
  transparent: true,
  opacity: 0.1, // 透明度不能为0 否则无法接收阴影
});
const plane = new THREE.Mesh(geometry, material);
plane.receiveShadow = true;
plane.castShadow = true;
plane.position.copy(planePosition);
plane.rotation.copy(planeRotation);
  • render 设置
renderer = new THREE.WebGLRenderer({
  antialias: !isIOS, //抗锯齿 IOS需关闭
  alpha: true,
  shadowMapEnabled: true, //使渲染器支持阴影贴图
});
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
  • 灯光设置
light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(-3, 10, -4); //设置灯光位置
scene.add(light); //添加到场景
light.castShadow = true; //设置灯光生成阴影
// 设置阴影映射参数
light.shadow.mapSize.width = 1024 * 4;
light.shadow.mapSize.height = 1024 * 4;
light.shadow.camera.near = 0.5;
light.shadow.camera.far = 80;
light.shadow.opacity = 1; // 设置阴影不透明
  • 模型上的设置
//设置模型生成阴影并接收阴影
model.castShadow = true;
model.receiveShadow = true;

?向后兼容

  • 由于我们是线下活动,人来人往,对稳定性要求极高,否则模型经常飘走无法完成拍照功能,所以我们需要优先使用 v2。
  • 但由于部分手机(比如 iphone13 mini)检测成功率低,总是无法放置模型体验也不好,所以我们做了版本降级的逻辑:当 v2 连续 x 次检测不到时,切换为 v1 版本。
  • 对于根本无法支持 AR 功能的情况,我们提供了自拍模式,用相机画面+动态贴图的方式来实现合影。

    有些手机,系统版本支持,但是硬件不支持,无法通过版本提前预判,只能在创建 AR 会话的回调函数中感知。

function compareVersion(targetVersion) {
  let fn = Taro.getAppBaseInfo ? Taro.getAppBaseInfo : Taro.getSystemInfoSync;
  let cur = fn().SDKVersion as string;
  let v1 = cur.split(".");
  let v2 = targetVersion.split(".");
  const len = Math.max(v1.length, v2.length);

  while (v1.length < len) {
    v1.push("0");
  }
  while (v2.length < len) {
    v2.push("0");
  }

  for (let i = 0; i < len; i++) {
    const num1 = parseInt(v1[i]);
    const num2 = parseInt(v2[i]);

    if (num1 > num2) {
      return 1;
    } else if (num1 < num2) {
      return -1;
    }
  }

  return 0;
}
enum VkVersion {
  V0 = "v0",
  V1 = "v1",
  V2 = "v2",
  V2_1 = "v2_1",
  Error = "v_error",
}

function setSupportVK() {
  let canUseV;
  if (compareVersion("2.20.0") < 0) {
    console.log("系统版本低,不支持 ar");
    canUseV = VkVersion.V0;
    showCantArToast(); //toast 提示用户不支持 AR
  } else {
    if (compareVersion("2.22.0") >= 0 && Taro.isVKSupport(VkVersion.V2)) {
      console.log({ title: "当前系统支持高版本AR", icon: "success" });
      canUseV = VkVersion.V2;
    } else {
      console.log("系统支持低版本 ar");
      canUseV = VkVersion.V1;
      showLowVersionModal(); //弹窗 提示用户使用低版本或使用自拍
    }
  }
  Taro.setStorageSync(VK_STORAGE_KEY, canUseV); //本地存储
  return canUseV;
}
function setErrSupportVK() {
  console.log("微信 api 识别支持,但是创建 ar 失败了");
  showCantArToast(); //toast 提示用户不支持 AR
  Taro.setStorageSync(VK_STORAGE_KEY, VkVersion.Error); //本地存储
}

想要更好的体验,只能期待(*❦ω❦)vision kit和硬件升级?

?内存问题解决

打开页面的次数增多,会造成小程序闪退,我们从内存释放角度解决。

  • AR 场景
if (session) {
  webglBusiness.dispose();
  session.destroy();
  session = null;
}
  • 3d 渲染
    three.js 会创建在渲染中所必需的特定对象,这些对象并不会被自动释放;相反,应用程序必须使用特殊的 API 来释放这些资源。官方说明在这里
// mesh 释放
if (modelGroup) {
  modelGroup.traverse((object) => {
    if (object.isMesh) {
      const { geometry, material, skeleton } = object;
      geometry.dispose(); //几何体释放
      material.dispose(); //材质释放
      Object.keys(material).forEach((propName) => {
        const propValue = material[propName];
        if (propValue && propValue.isTexture) {
          propValue.dispose(); //纹理释放
        }
      });
      if (skeleton && skeleton.boneTexture) {
        skeleton.boneTexture.dispose(); //骨骼纹理释放
      }
    }
  });
  scene.remove(modelGroup);
  modelGroup = null;
}

// 灯光阴影
if (light) {
  light.shadow.map.dispose();
  scene.remove(light);
  light = null;
}

// 清除场景本身
if (scene) {
  scene.dispose();
  scene = null;
}


// render 释放
if (renderer) {
  renderer.dispose();
  renderer = null;
}

做了以上这些卸载,有一定作用,但是加载模型还是会有内存问题,希望大佬们在评论区给些建议

?重置(平面检测后重新放置模型)与平移

开发过程中遇到,重置与平移功能无法完美共存的问题。以手指向左滑动,模型左移为例子来进行下面的说明。

问题描述

相机打开瞬间(朝向正东方)创建了世界坐标系,模型刚好朝着相机,监听 onTouchMove,获取手指滑动距离 dir,调用 model.position.x=model.position.x-dir。模型的表现为向左平移。
当手机姿态发生改变,比如朝向正西方,放置模型。模型会朝着相机,但是手指向左滑动时,模型向右移动。

解决方案

  • 实时计算正确方位

拿到相机和模型在 3d 空间的坐标,根据模型与相机的位置关系实时计算模型应该移动的方向。以模型位置指向相机位置的向量为模型的前方,后、左、右依次类推。手指上、下、左、右滑动时,对应的模型平移方向分别为前方,后、左、右。
然后踩坑了,相机和模型拿到的位置坐标永远都是 0 0 0,大无语。

  • 反复销毁创建 AR 场景

每次放置模型时,都重新创建 AR 场景,创建完立刻执行 hitTest,把模型加到场景中。方向是对了。
但是!销毁创建会导致画面闪烁,并且那些不容易检测的手机再也检测不到平面了。

  • 刷新

一切重来!对模型数据进行缓存,每次放置模型时都刷新一次页面,重新创建 AR 场景,创建完立刻执行 hitTest。方向肯定是对的啦,也不会画面闪烁。
但那些不容易检测的手机还是再也检测不到平面。

最优解:毁灭吧!

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MY6HcJ3h' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片