原来地图导航结合WebAR技术还能这么玩

如下内容转载自多多洛爱学习的文章《WebAR技术探索-导航中的应用》
做者:多多洛爱学习
连接: http://www.javashuo.com/article/p-wyiolwvp-hp.html
来源:掘金
著做权归做者全部。商业转载请联系做者得到受权,非商业转载请注明出处。

本文探索在Web前端实现AR导航效果的前沿技术和难点。html

1. AR简介

加强现实(Augmented Reality,简称AR):是一种实时地计算摄影机影像的位置及角度并加上相应图像、视频、3D模型的技术,这种技术的目标是在屏幕上把虚拟世界套在现实世界并进行互动。前端

通常在web中实现AR效果的主要步骤以下:html5

  1. 获取视频源
  2. 识别marker
  3. 叠加虚拟物体
  4. 显示最终画面

AR导航比较特殊的地方是,它并不是经过识别marker来肯定虚拟物体的叠加位置,而是经过定位将虚拟和现实联系在一块儿,主要步骤以下:web

  1. 获取视频源
  2. 坐标系转换:编程

    1. 获取设备和路径的绝对定位
    2. 计算路径中各标记点与设备间的相对定位
    3. 在设备坐标系中绘制标记点
  3. 3D图像与视频叠加
  4. 更新定位和设备方向,控制Three.js中的相机移动

2. 技术难点

如上文所述AR导航的主要步骤,其中难点在于:浏览器

  1. 兼容性问题
  2. WebGL三维做图
  3. 定位的精确度和轨迹优化
  4. 虚拟和现实单位尺度的映射

2.1 兼容性问题:

不一样设备不一样操做系统以及不一样浏览器带来的兼容性问题主要体如今对获取视频流和获取设备陀螺仪信息的支持上。安全

2.1.1 获取视频流

  1. Navigator API兼容处理

    navigator.getUserMedia()已不推荐使用,目前新标准采用navigator.mediaDevices.getUserMedia()。但是不一样浏览器对新方法的支持程度不一样,须要进行判断和处理。同时,若是采用旧方法,在不一样浏览器中方法名称也不尽相同,好比webkitGetUserMedia微信

//不支持mediaDevices属性
    if (navigator.mediaDevices === undefined) {
      navigator.mediaDevices = {};
    }

//不支持mediaDevices.getUserMedia
    if (navigator.mediaDevices.getUserMedia === undefined) {
        navigator.mediaDevices.getUserMedia = function(constraints) {
            var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;

            if(!getUserMedia) {
                return Promise.reject(new Error('getUserMedia is not implemented in this browser'));
            }

            return new Promise(function(resolve, reject) {
                getUserMedia.call(navigator, constraints, resolve, reject);
            });
        }
    }
  1. 参数兼容处理

    getUserMedia接收一个MediaStreamConstraints类型的参数,该参数包含两个成员videoaudioapp

var constraints = {
        audio: true,
        video: {
            width: { 
                min: 1024,
                ideal: 1280,
                max: 1920
            },
            height: 720,
            frameRate: {
                ideal: 10,
                max: 15
            },
            facingMode: "user" // user/environment,设置先后摄像头
        }
    }

在使用WebAR导航时,须要调取后置摄像头,然而facingMode参数目前只有Firefox和Chrome部分支持,对于其余浏览器(微信、手Q、QQ浏览器)须要另外一个参数optional.sourceId,传入设备媒体源的id。经测试,该方法在不一样设备不一样版本号的微信和手Q上表现有差别。dom

if(MediaStreamTrack.getSources) {
        MediaStreamTrack.getSources(function (sourceInfos) {
            for (var i = 0; i != sourceInfos.length; ++i) {
                var sourceInfo = sourceInfos[i];
                //这里会遍历audio,video,因此要加以区分  
                if (sourceInfo.kind === 'video') {  
                    exArray.push(sourceInfo.id);  
                }  
            }
            constraints = { 
                video: {  
                    optional: [{  
                        sourceId: exArray[1] //0为前置摄像头,1为后置
                    }]  
                }
            };
        });
    } else {
        constraints = { 
            video: {
                facingMode: {
                    exact: 'environment'
                }
            }
        });
    }
  1. 操做系统的兼容性问题

    因为苹果的安全机制问题,iOS设备任何浏览器都不支持getUserMedia()。因此没法在iOS系统上实现WebAR导航。

  2. 协议

    出于安全考虑,Chrome47以后只支持HTTPS页面获取视频源。

2.1.2 获取设备转动角度

设备的转动角度表明了用户的视角,也是链接虚拟和现实的重要参数。HTML5提供DeviceOrientation API能够实时获取设备的旋转角度参数。经过监听deviceorientation事件,返回DeviceOrientationEvent对象。

{
    absolute: [boolean] 是否为绝对转动值
    alpha: [0-360]
    beta: [-180-180]
    gamma: [-90-90]
}

其中alpha、beta、gamma是咱们想要获取的角度,它们各自的意义能够参照下图和参考文章:

image
陀螺仪的基本知识

然而iOS系统的webkit内核浏览器中,该对象还包括webkitCompassHeading成员,其值为设备与正北方向的偏离角度。同时iOS系统的浏览器中,alpha并不是绝对角度,而是以开始监听事件时的角度为零点。

Android系统中,咱们可使用-alpha获得设备与正北方的角度,可是就目前的测试状况看来,该值并不稳定。因此在测试Demo中加入了手动校订alpha值的过程,在导航开始前将设备朝向正北方来获取绝对0度,虽然不严谨但效果还不错。

image

2.2 WebGL三维做图

WebGL是在浏览器中实现三维效果的一套规范,AR导航须要绘制出不一样距离不一样角度的标记点,就须要三维效果以适应真实场景视频流。然而WebGL原生的接口很是复杂,Three.js是一个基于WebGL的库,它对一些原生的方法进行了简化封装,使咱们可以更方便地进行编程。

Three.js中有三个主要概念:

  1. 场景(scene):物体的容器,咱们要绘制标记点就是在场景中添加指定坐标和大小的球体
  2. 相机(camera):模拟人的眼睛,决定了呈现哪一个角度哪一个部分的场景,在AR导航中,咱们主要经过相机的移动和转动来模拟设备的移动和转动
  3. 渲染器(renderer):设置画布,将相机拍摄的场景呈如今web页面上

在AR导航的代码中,我对Three.js的建立过程进行了封装,只需传入DOM元素(通常为<div>,做为容器)和参数,自动建立三大组件,并提供了Three.addObjectThree.renderThree等接口方法用于在场景中添加/删除物体或更新渲染等。

function Three(cSelector, options) {
    var container = document.querySelector(cSelector);
    // 建立场景
    var scene = new THREE.Scene();
    // 建立相机
    var camera = new THREE.PerspectiveCamera(options.camera.fov, options.camera.aspect, options.camera.near, options.camera.far);
    // 建立渲染器
    var renderer = new THREE.WebGLRenderer({
        alpha: true
    });
    // 设置相机转动控制器
    var oriControls = new THREE.DeviceOrientationControls(camera);
    // 设置场景大小,并添加到页面中
    renderer.setSize(container.clientWidth, container.clientHeight);
    renderer.setClearColor(0xFFFFFF, 0.0);
    container.appendChild(renderer.domElement);

    // 暴露在外的成员
    this.main = {
        scene: scene,
        camera: camera,
        renderer: renderer,
        oriControls: oriControls,
    }
    this.objects = [];
    this.options = options;
}
Three.prototype.addObject = function(type, options) {...} // 向场景中添加物体,type支持sphere/cube/cone
Three.prototype.popObject = function() {...} // 删除场景中的物体
Three.prototype.setCameraPos = function(position) {...} // 设置相机位置
Three.prototype.renderThree = function(render) {...} // 渲染更新,render为回调函数
Three.prototype.setAlphaOffset = function(offset) {..} // 设置校订alpha的偏离角度

在控制相机的转动上,我使用了DeviceOrientationControls,它是Three.js官方提供的相机跟随设备转动的控制器,实现对deviceorientation的侦听和对DeviceOrientationEvent的欧拉角处理,并控制相机的转动角度。只需在渲染更新时调用一下update方法:

three.renderThree(function(objects, main) {
    animate();
    function animate() {
        window.requestAnimationFrame(animate);
        main.oriControls.update();
        main.renderer.render(main.scene, main.camera);
    }
});

2.3 定位的精确度和轨迹优化

咱们的调研中目前有三种获取定位的方案:原生navigator.geolocation接口,腾讯前端定位组件,微信JS-SDK地理位置接口:

  1. 原生接口

    navigator.geolocation接口提供了getCurrentPositionwatchPosition两个方法用于获取当前定位和监听位置改变。通过测试,Android系统中watchPosition更新频率低,而iOS中更新频率高,但抖动严重。

  2. 前端定位组件

    使用前端定位组件须要引入JS模块(https://3gimg.qq.com/lightmap/components/geolocation/geolocation.min.js),经过 qq.maps.Geolocation(key, referer)构造对象,也提供getLocationwatchPosition两个方法。通过测试,在X5内核的浏览器(包括微信、手Q)中,定位组件比原生接口定位更加准确,更新频率较高。

  3. 微信JS-SDK地理位置接口

    使用微信JS-SDK接口,咱们能够调用室内定位达到更高的精度,可是须要绑定公众号,只能在微信中使用,仅提供getLocation方法,暂时不考虑。

    综上所述,咱们主要考虑在X5内核浏览器中的实现,因此选用腾讯前端定位组件获取定位。可是在测试中仍然暴露出了定位不许确的问题:

    1. 定位不许致使虚拟物体与现实没法准确叠加
    2. 定位的抖动致使虚拟标记点跟随抖动,移动视觉效果不够平稳

针对该问题,我设计了优化轨迹的方法,进行定位去噪、肯定初始中心点、根据路径吸附等操做,以实现移动时的变化效果更加平稳且准确。

2.3.1 定位去噪

咱们经过getLocationwatchPosition方法获取到的定位数据包含以下信息:

{
    accuracy: 65,
    lat: 39.98333,
    lng: 116.30133
    ...
}

其中accuracy表示定位精度,该值越低表示定位越精确。假设定位精度在固定的设备上服从正态分布(准确来讲应该是正偏态分布),统计整条轨迹点定位精度的均值mean和标准差stdev,将轨迹中定位精度大于mean + (1~2) * stdev的点过滤掉。或者采用箱型图的方法去除噪声点。

2.3.2 初始点肯定

初始点很是重要,若初始点偏离,则路线不许确、虚拟现实没法重叠、没法获取到正确的移动路线。测试中我发现定位开始时得到的定位点大多不太准确,因此须要一段时间来肯定初始点。

定位开始,设置N秒用以获取初始定位。N秒钟获取到的定位去噪以后造成一个序列track_denoise = [ loc0, loc1, loc2...],对该序列中的每个点计算其到其余点的距离之和,并加上自身的定位精度,获得一个中心衡量值,而后取衡量值最小的点为起始点。

image

2.3.3 基于路线的定位校订

基于设备始终跟随规划路线进行移动的假设,能够将定位点吸附到规划路线上以防止3D图像的抖动。

以下图所示,以定位点到线段的映射点做为校订点。路线线段的选择依据以下:

  1. 初始状态:以起始点与第二路线点之间的线段为当前线段,cur = 0; P_cur = P[cur];
  2. 在第N条线段上移动时,若映射长度(映射点与线段起点的距离)为负,校订点取当前线段的起点,线路回退至上一线段,cur = N - 1; P_cur = P[cur];;若映射长度大于线段长度,则校订点取当前线段的终点,线路前进至下一线段,cur = N + 1; P_cur = P[cur];
  3. 若当前线段与下一线段的有效范围有重叠区域(以下图绿色阴影区),则需判判定位点到两条线段的距离,以较短的为准,肯定校订点和线路选择。

image

2.4 虚拟和现实的单位长度映射

WebGL中的单位长度与现实世界的单位长度并无肯定的映射关系,暂时还没法准确进行映射。经过测试,暂且选择1(米):15(WebGL单位长度)。

3. demo演示

演示视频:WebAR技术探索-导航中的应用

对地图感兴趣的开发者,欢迎登陆腾讯位置服务体验~

相关文章
相关标签/搜索