Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> [OpenGL]從零開始寫一個Android平台下的全景視頻播放器——4.1 傳感器類型以及數據收集方法

[OpenGL]從零開始寫一個Android平台下的全景視頻播放器——4.1 傳感器類型以及數據收集方法

編輯:關於Android編程

傳感器的使用是全景視頻能夠帶來一定程度的“沉浸感”的關鍵,我們希望當用戶轉身時,視頻也能夠跟著轉,而不是用戶手動拖動(廢話。。)

由於傳感器是硬件或軟件模擬的設備,以下討論的內容大部分僅適用於Android平台。

類型

Android平台下的傳感器類型很多,一部分是硬件傳感器,還有一部分是在硬件傳感器的基礎上模擬的軟件傳感器(詳見谷歌開發者網站),我們只需要用到其中的幾個(以下介紹可能不適用於其他平台,請注意區別),下面的前三個傳感器通常為硬件傳感器:

TYPE_GYROSCOPE(陀螺儀)
陀螺儀提供的是手機相對於自身的旋轉角速度,精度和刷新率都很高,由於是相對自身,所以僅僅依靠陀螺儀不能確定手機朝向,不能用來做VR

TYPE_MAGNETIC_FIELD(磁力計)
磁力計提供的是XYZ軸上的磁感應強度,坐標系定義會在後面講述
平常,磁力計往往以電子羅盤(指南針)的身份出現。磁力計的精度不高並且很容易受到干擾,所以我們讀取出來的數據其實並不是真正的傳感器數據,而是做了硬件矯正的結果
(參見TYPE_MAGNETIC_FIELD_UNCALIBRATED)。

TYPE_ACCELEROMETER(加速度計)
咦,不就是常見的重力感應嘛,這可是最低端的設備都會有的傳感器。
加速度計獲取的數值是重力和加速度的疊加結果,精度和刷新率都尚可(其實也和設備有關),在加速度計上往往可以做很多文章,例如加個低通濾波求加速度,或者求重力(不過,這個一般都會提供硬件或軟件模擬的傳感器,不需要自己寫),甚至移動距離(存在大的可怕的積分誤差)
雖然加速度計沒有陀螺儀看起來那麼高大上,但是沒有加速度計還真不行

TYPE_ROTATION_VECTOR(旋轉矢量傳感器)
實際上,在代碼實現的時候,我們往往會只用這個傳感器,旋轉矢量傳感器是以上三個傳感器在硬件(或軟件)層面的融合,直接求出了手機的旋轉姿態,並且用旋轉矢量來表示,旋轉矢量還可以很方便的轉換為四元數、旋轉矩陣、歐拉角,簡直好用的不要不要的,都不用自己做傳感器數據融合了(其實這才是最困難的部分)

補充

可以看出,磁力計和加速度計都是相對於一個絕對的坐標系的,而陀螺儀則相對於手機自身,也就是說,陀螺儀的參考系是綁在手機上的。
另外,上面的幾個傳感器,雖然定義各有不同,但是從傳感器獲取到的數據都是三個(XYZ,是不是很方便,應該說是我們只用到三個)
加速度計幫助確定一個絕對的方向,然後陀螺儀就在這個方向上累計旋轉的角度,鑒於陀螺儀的精度和刷新率都很高,一般都能達到較好的效果。

題外話(不關心傳感器的請忽略)

真的要磁力計嗎

除了旋轉矢量傳感器,其實還有一種TYPE_GAME_ROTATION_VECTOR,這個傳感器只融合了加速度計和陀螺儀,也是Google的Cardboard VR中采用的傳感器(Cardboard自己模擬了一個TYPE_GAME_ROTATION_VECTOR,因為並不是每個手機都支持),因為VR中的初始角度(朝南朝東,即Y軸朝向)一般是固定的,由於這種解決方案需要的傳感器數目少(只要兩個硬件傳感器),還是有一些優勢的
所以,其實我們並不需要磁力計也能達到很滿意的效果,是麼?

關於漂移

不用磁力計也是會出現一些問題的,例如漂移——我明明手機放著沒動啊,場景怎麼就自己轉走了呢?
這個現象往往出現於使用的傳感器比較次的手機上,例如魅藍系列和紅米系列(這個真不是黑。。當然藍綠大廠就只能呵呵了),如前所述,雖然陀螺儀精度比較高,但是陀螺儀描述的只是相對於手機自身的角速度,所以在國內“友商”的大力協助下,我們的VR場景就漂走了。
可能有朋友會說,可以校准啊!!用卡爾曼濾波,用&()&**#@#@
由於加速度計只能校准重力方向,所以Y軸的朝向才會漂走,當然使用濾波方案是可以有效緩解這個問題的,但是,我們只要把磁力計加回來問題不就解決了麼?Google都幫我們寫好啦,旋轉矢量傳感器啊~

關於初始角度

Cardboard的解決方案(只是討論Gyro+Acc的方案,其他的暫不考慮)的優點在於,因為Y軸是未定義的,所以可以自定義用戶進入場景時的旋轉角,這點在VR中是很重要的。而旋轉矢量傳感器由於使用了磁力計校正,Y軸的朝向已經固定了?
但是,難道用ROTATION_VECTOR就不行了嗎?當然也是可以的,只是會麻煩一些,具體的可以我參考項目中的代碼,這裡不展開敘述。

真的要陀螺儀嘛

肯定會有人想,這個問題居然會被提出來,真是搞笑。。哪有VR不用陀螺儀的啊!!!
但是就實現效果而言,陀螺儀還真不是必要的,因為我發現了一個叫iGyro的東西,這個“良心”廠家使用了加速度計和磁力計結合的方法來模擬出陀螺儀的效果,“大大降低了成本”,“被國內友商廣泛采用”,還美其名曰“軟件陀螺儀”
不過為什麼這樣是可行的呢?因為用加速度和磁力計就已經可以求出手機姿態了
那陀螺儀有什麼用?
因為在大部分應用中,求出旋轉姿態是不夠的,我們需要的是穩定並且能夠快速響應的姿態,這個時候陀螺儀就派上用場了。(還要感謝豐富的傳感器數據融合方案)
換句話說,陀螺儀是帶來“絲般順滑”體驗的關鍵,因此陀螺儀是必須的,這也就是為什麼很多看上去有陀螺儀手機往往並不能體驗VR的原因
(當然,本系列文章所說的VR,其實算不上什麼VR)

關於九軸

什麼是九軸?九軸(9DoF,9-degree-of-freedom)就是三軸磁場 + 三軸加速度+三軸陀螺儀的數據融合,這個和飛行器的六軸、八軸並沒有什麼關系。。

關於IMU

IMU(Inertial measurement unit),好吧,其實往往和九軸是一個東西,但是高精度的IMU往往能玩出手機這種渣渣傳感器無法達成的花樣,飛行器、機器人導航、慣性制導往往就需要效果較好的IMU的配合

使用傳感器

說了這麼多,我們來看看Android平台下傳感器應該怎麼用吧,下面是一段摘自Android Developers的代碼

public class SensorActivity extends Activity implements SensorEventListener {
     private final SensorManager mSensorManager;
     private final Sensor mAccelerometer;

     public SensorActivity() {
         mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
         mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
     }

     protected void onResume() {
         super.onResume();
         mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
     }

     protected void onPause() {
         super.onPause();
         mSensorManager.unregisterListener(this);
     }

     public void onAccuracyChanged(Sensor sensor, int accuracy) {
     }

     public void onSensorChanged(SensorEvent event) {
     }
 }

我們需要SensorManager來獲取具體的傳感器實例,並且要用registerListener進行傳感器的注冊,同時傳入的還有一個監聽器SensorEventListener(在這裡就是this了),用來獲取傳感器的數據以及精度變化等

由於傳感器是硬件資源,所以往往應該在應用不在前台的時候就釋放掉(和Camera一樣)

收集數據

我們以ROTATION_VECTOR為例說明一下在全景視頻播放器中使用傳感器的流程

package com.martin.ads.vrlib;

import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;

import com.martin.ads.vrlib.constant.Constants;
import com.martin.ads.vrlib.utils.SensorUtils;
import com.martin.ads.vrlib.utils.StatusHelper;


/**
 * Project: Pano360
 * Package: com.martin.ads.pano360
 * Created by Ads on 2016/5/2.
 */
public class SensorEventHandler implements SensorEventListener {

    public static String TAG = "SensorEventHandler";

    private float[] rotationMatrix = new float[16];

    //private float[] orientationData=new float[3];

    private StatusHelper statusHelper;
    private SensorHandlerCallback sensorHandlerCallback;

    private boolean sensorRegistered;
    private SensorManager sensorManager;

    public void init(){
        sensorRegistered=false;
        sensorManager = (SensorManager) statusHelper.getContext()
                .getSystemService(Context.SENSOR_SERVICE);
        Sensor sensorRot = sensorManager.getDefaultSensor(Constants.SENSOR_ROT);
        if (sensorRot==null) return;
        sensorManager.registerListener(this, sensorRot, SensorManager.SENSOR_DELAY_GAME);
        sensorRegistered=true;
    }

    public void releaseResources(){
        if (!sensorRegistered || sensorManager==null) return;
        sensorManager.unregisterListener(this);
        sensorRegistered = false;
    }


    @Override
    public void onSensorChanged(SensorEvent event) {
        if (event.accuracy != 0){
            int type = event.sensor.getType();
            switch (type){
                case Constants.SENSOR_ROT:
                    SensorUtils.sensorRotationVectorToMatrix(event,rotationMatrix);
                    sensorHandlerCallback.updateSensorMatrix(rotationMatrix);
                break;
            }
        }
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {

    }

    public void setSensorHandlerCallback(SensorHandlerCallback sensorHandlerCallback){
        this.sensorHandlerCallback=sensorHandlerCallback;
    }

    public void setStatusHelper(StatusHelper statusHelper){
        this.statusHelper=statusHelper;
    }

    public interface SensorHandlerCallback{
        void updateSensorMatrix(float[] sensorMatrix);
    }
}

我們按套路去獲取了一個旋轉矢量傳感器,並且注冊了監聽器,然後在傳感器事件中,通過event.sensor.getType()來判斷是否是我們需要的傳感器,獲取到數據以後,我們將旋轉矢量轉換成旋轉矩陣,以便在OpenGL中使用。

幸運的是,我們只需要對於轉換出的旋轉矩陣做少量處理,就可以在OpenGL中用了

Github項目地址

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved