読者です 読者をやめる 読者になる 読者になる

うさがにっき

読書感想文とプログラムのこと書いてきます

Tango現状まとめ

Androidd AR

概要

発表からしばらく動きがなかったProjectTangoがようやく今年の秋に端末発売となる(日本発売は未定)
発売前に現在わかっていることをまとめる

* 詳細

What is Tango

get.google.com
スマートフォンに深度センサーなど通常のスマホには無いセンサーを付け、それらからスマートフォンにより多くの情報を認識できるようにした端末をTango対応端末という(Daydream対応端末的な)
それらの情報を使ってMotionTracking, AreaLearning, DepthPerceptionなどの機能を実現できる
www.youtube.com

既に海外では導入実績あり
Matterport and Google ATAP Project Tango - Matterport

開発端末

開発用端末が公開されているが日本では購入不可
Tango タブレット開発キット - このキットはデベロッパーを対象としています* - Google ストア
今秋Phab2 ProというTango端末がLenovoより発売される予定、しかしこれも日本で発売されるかは未定
Lenovo Phab 2 Pro | 世界初の Tango 対応スマートフォン | レノボジャパン

開発環境

開発用SDKは既に公開されている
Download the Tango SDK  |  Tango  |  Google Developers
リリースノートを見ると月に一度ほどアップデートされており活発
Release Notes  |  Tango  |  Google Developers

すでにTango用アプリはいくつかリリースされている
Project Tango - Google Play の Android アプリ
開発用アプリもいくつかリリースされている様子
Project Tango Experiments - Google Play の Android アプリ

サンプルアプリは以下からダウンロード可能・・・が現状輸入するなどしないと開発端末は手に入らない、エミュレーターなどもないので動かすことはできない
GitHub - googlesamples/tango-examples-java: Example projects for Project Tango Java API

Tangoの各機能

各機能の紹介とソースコードを眺めてみる
ちなみに各昨日のベストプラクティス的なのがある
Tango UX Best Practices  |  Tango  |  Google Developers

MotionTracking概要

Motion Tracking  |  Tango  |  Google Developers
端末が領域の中でどのように動いているかを検知する機能

MotionTrackingソース

onResume抜粋

   @Override
    protected void onResume() {
        super.onResume();

        // Initialize Tango Service as a normal Android Service, since we call
        // mTango.disconnect() in onPause, this will unbind Tango Service, so
        // every time when onResume gets called, we should create a new Tango object.
        mTango = new Tango(MotionTrackingActivity.this, new Runnable() {
            // Pass in a Runnable to be called from UI thread when Tango is ready,
            // this Runnable will be running on a new thread.
            // When Tango is ready, we can call Tango functions safely here only
            // when there is no UI thread changes involved.
            @Override
            public void run() {
                mConfig = setupTangoConfig(mTango);

                try {
                    setTangoListeners();
                } catch (TangoErrorException e) {
                    Log.e(TAG, getString(R.string.exception_tango_error), e);
                } catch (SecurityException e) {
                    Log.e(TAG, getString(R.string.permission_motion_tracking), e);
                }
                try {
                    mTango.connect(mConfig);
                } catch (TangoOutOfDateException e) {
                    Log.e(TAG, getString(R.string.exception_out_of_date), e);
                } catch (TangoErrorException e) {
                    Log.e(TAG, getString(R.string.exception_tango_error), e);
                }
            }
        });
    }

MotionTracking初期設定

    private TangoConfig mConfig;

    /**
     * Sets up the tango configuration object. Make sure mTango object is initialized before
     * making this call.
     */
    private TangoConfig setupTangoConfig(Tango tango) {
        // Create a new Tango Configuration and enable the MotionTrackingActivity API
        TangoConfig config = new TangoConfig();
        config = tango.getConfig(config.CONFIG_TYPE_DEFAULT);
        config.putBoolean(TangoConfig.KEY_BOOLEAN_MOTIONTRACKING, true);

        // Tango service should automatically attempt to recover when it enters an invalid state.
        config.putBoolean(TangoConfig.KEY_BOOLEAN_AUTORECOVERY, true);
        return config;
    }

取得しているconfig = tango.getConfig(config.CONFIG_TYPE_DEFAULT)には他にもいろいろある
CONFIG_TYPE_DEFAULTはMOTION_TRACKINGは取得できる
TangoConfig  |  Tango Java API  |  Google Developers

起動する前に以下二つの値にtrueを設定する必要がある

  • TangoConfig. KEY_BOOLEAN_MOTIONTRACKING
  • TangoConfig.KEY_BOOLEAN_AUTORECOVERY

setTangoListenersを設定する
ここで初期化した結果、イベントをキャッチできる

    /**
     * Set up the callback listeners for the Tango service, then begin using the Motion
     * Tracking API. This is called in response to the user clicking the 'Start' Button.
     */
    private void setTangoListeners() {
        // Lock configuration and connect to Tango
        // Select coordinate frame pair
        final ArrayList<TangoCoordinateFramePair> framePairs =
                new ArrayList<TangoCoordinateFramePair>();
        framePairs.add(new TangoCoordinateFramePair(
                TangoPoseData.COORDINATE_FRAME_START_OF_SERVICE,
                TangoPoseData.COORDINATE_FRAME_DEVICE));

        // Listen for new Tango data
        mTango.connectListener(framePairs, new OnTangoUpdateListener() {
            @Override
            public void onPoseAvailable(final TangoPoseData pose) {
                logPose(pose);
            }

            @Override
            public void onXyzIjAvailable(TangoXyzIjData arg0) {
                // We are not using TangoXyzIjData for this application
            }

            @Override
            public void onTangoEvent(final TangoEvent event) {
            }

            @Override
            public void onFrameAvailable(int cameraId) {
                // We are not using onFrameAvailable for this application
            }
        });
    }

まずMotionTrackingをどこを基準にするか、どのタイミングで更新するかをTangoCoordinateFramePairで設定する
どこを基準にするかは以下の二つを設定できる

  • COORDINATE_FRAME_START_OF_SERVICE

MotionTrackingスタート時

  • COORDINATE_FRAME_PREVIOUS_DEVICE_POSE

直前にDeviceが静止した時

どのタイミングで更新するかは以下を設定できる、他にもいろいろありそう

  • COORDINATE_FRAME_DEVICE

Deviceの更新タイミング

  • COORDINATE_FRAME_IMU

COORDINATE_FRAME_DEVICEとの違いがよくわからないが、内部更新タイミングとあった

  • COORDINATE_FRAME_DISPLAY

Display更新タイミング

Frameについては以下も参照
Frames of Reference  |  Tango  |  Google Developers

またMotionTrackingには三つの状態がある

  • TangoPoseData.POSE_INITIALIZING

初期化前、まだ使用できない

  • TangoPoseData.POSE_VALID

普通に使用できる

  • TangoPoseData.POSE_INVALID

値(取得された値?)が向こうなので再起動が必要
再起動の方法はTangoConfig.KEY_BOOLEAN_AUTORECOVERYに設定されている値によって自動再起動するかどうか設定できる

後は取得された
TangoXyzIjData  |  Tango Java API  |  Google Developers
とか
TangoEvent  |  Tango Java API  |  Google Developers
を使って処理を行う

AreaLearning概要

Area Learning  |  Tango  |  Google Developers
物理的な空間を端末内の仮想的な空間に記憶する機能

AreaLearningを使う上でkeyとなる機能はMotionTrackingを使った以下の機能になる

  • drift corrections

軌跡の精度の向上

  • localization

認識済みの場所のどこにいるか把握する

AreaLearnigソース確認

onResume抜粋

    @Override
    protected void onResume() {
        super.onResume();

        // Initialize Tango Service as a normal Android Service, since we call
        // mTango.disconnect() in onPause, this will unbind Tango Service, so
        // everytime when onResume gets called, we should create a new Tango object.
        mTango = new Tango(AreaDescriptionActivity.this, new Runnable() {
            // Pass in a Runnable to be called from UI thread when Tango is ready,
            // this Runnable will be running on a new thread.
            // When Tango is ready, we can call Tango functions safely here only
            // when there is no UI thread changes involved.
            @Override
            public void run() {
                mConfig = setTangoConfig(mTango, mIsLearningMode, mIsConstantSpaceRelocalize);

                synchronized (AreaDescriptionActivity.this) {
                    // Re-attach listeners.
                    try {
                        setUpTangoListeners();
                    } catch (TangoErrorException e) {
                        Log.e(TAG, getString(R.string.tango_error), e);
                    } catch (SecurityException e) {
                        Log.e(TAG, getString(R.string.no_permissions), e);
                    }

                    // Connect to the tango service (start receiving pose updates).
                    try {
                        mTango.connect(mConfig);
                    } catch (TangoOutOfDateException e) {
                        Log.e(TAG, getString(R.string.tango_out_of_date_exception), e);
                    } catch (TangoErrorException e) {
                        Log.e(TAG, getString(R.string.tango_error), e);
                    } catch (TangoInvalidException e) {
                        Log.e(TAG, getString(R.string.tango_invalid), e);
                    }
                }

                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        synchronized (AreaDescriptionActivity.this) {
                            setupTextViewsAndButtons(mTango, mIsLearningMode,
                                    mIsConstantSpaceRelocalize);
                        }
                    }
                });
            }
        });
    }


AreaLearning初期設定

    /**
     * Sets up the tango configuration object. Make sure mTango object is initialized before
     * making this call.
     */
    private TangoConfig setTangoConfig(Tango tango, boolean isLearningMode, boolean isLoadAdf) {
        TangoConfig config;
        config = tango.getConfig(TangoConfig.CONFIG_TYPE_DEFAULT);
        // Check if learning mode
        if (isLearningMode) {
            // Set learning mode to config.
            config.putBoolean(TangoConfig.KEY_BOOLEAN_LEARNINGMODE, true);

        }
        // Check for Load ADF/Constant Space relocalization mode
        if (isLoadAdf) {
            ArrayList<String> fullUuidList;
            // Returns a list of ADFs with their UUIDs
            fullUuidList = tango.listAreaDescriptions();
            // Load the latest ADF if ADFs are found.
            if (fullUuidList.size() > 0) {
                config.putString(TangoConfig.KEY_STRING_AREADESCRIPTION,
                        fullUuidList.get(fullUuidList.size() - 1));
            }
        }
        return config;
    }

areaLearningを設定する
TangoConfig  |  Tango Java API  |  Google Developers

ADFファイルが必要になる場合は
TangoConfig.KEY_STRING_AREADESCRIPTIONにuuidを設定する
ADFファイルとはareaLearningした結果を保存したファイル、Tango APIを使用することにより保存できる

tangoListenerの設定

    private void setUpTangoListeners() {
        // Set Tango Listeners for Poses Device wrt Start of Service, Device wrt
        // ADF and Start of Service wrt ADF
        ArrayList<TangoCoordinateFramePair> framePairs = new ArrayList<TangoCoordinateFramePair>();
        framePairs.add(new TangoCoordinateFramePair(
                TangoPoseData.COORDINATE_FRAME_START_OF_SERVICE,
                TangoPoseData.COORDINATE_FRAME_DEVICE));
        framePairs.add(new TangoCoordinateFramePair(
                TangoPoseData.COORDINATE_FRAME_AREA_DESCRIPTION,
                TangoPoseData.COORDINATE_FRAME_DEVICE));
        framePairs.add(new TangoCoordinateFramePair(
                TangoPoseData.COORDINATE_FRAME_AREA_DESCRIPTION,
                TangoPoseData.COORDINATE_FRAME_START_OF_SERVICE));

        mTango.connectListener(framePairs, new OnTangoUpdateListener() {
            @Override
            public void onXyzIjAvailable(TangoXyzIjData xyzij) {
                // Not using XyzIj data for this sample
            }

            // Listen to Tango Events
            @Override
            public void onTangoEvent(final TangoEvent event) {
            }

            @Override
            public void onPoseAvailable(TangoPoseData pose) {
                // Make sure to have atomic access to Tango Data so that
                // UI loop doesn't interfere while Pose call back is updating
                // the data.
                synchronized (mSharedLock) {
                    // Check for Device wrt ADF pose, Device wrt Start of Service pose,
                    // Start of Service wrt ADF pose (This pose determines if the device
                    // is relocalized or not).
                    if (pose.baseFrame == TangoPoseData.COORDINATE_FRAME_AREA_DESCRIPTION
                            && pose.targetFrame == TangoPoseData
                            .COORDINATE_FRAME_START_OF_SERVICE) {
                        if (pose.statusCode == TangoPoseData.POSE_VALID) {
                            mIsRelocalized = true;
                        } else {
                            mIsRelocalized = false;
                        }
                    }
                }

                final double deltaTime = (pose.timestamp - mPreviousPoseTimeStamp) *
                        SECS_TO_MILLISECS;
                mPreviousPoseTimeStamp = pose.timestamp;
                mTimeToNextUpdate -= deltaTime;

                if (mTimeToNextUpdate < 0.0) {
                    mTimeToNextUpdate = UPDATE_INTERVAL_MS;

                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            synchronized (mSharedLock) {
                                mSaveAdfButton.setEnabled(mIsRelocalized);
                                mRelocalizationTextView.setText(mIsRelocalized ?
                                        getString(R.string.localized) :
                                        getString(R.string.not_localized));
                            }
                        }
                    });
                }
            }

            @Override
            public void onFrameAvailable(int cameraId) {
                // We are not using onFrameAvailable for this application.
            }
        });
    }
depthPerception概要

Depth Perception  |  Tango  |  Google Developers
現実世界とのオブジェクトとの距離を把握できる機能
範囲は0.5~4m

depthPerceptionソース確認

いつも通りonResume

    @Override
    protected void onResume() {
        super.onResume();

        // Initialize Tango Service as a normal Android Service, since we call
        // mTango.disconnect() in onPause, this will unbind Tango Service, so
        // every time when onResume gets called, we should create a new Tango object.
        mTango = new Tango(DepthPerceptionActivity.this, new Runnable() {
            // Pass in a Runnable to be called from UI thread when Tango is ready,
            // this Runnable will be running on a new thread.
            // When Tango is ready, we can call Tango functions safely here only
            // when there is no UI thread changes involved.
            @Override
            public void run() {
                mConfig = setupTangoConfig(mTango);

                try {
                    setTangoListeners();
                } catch (TangoErrorException e) {
                    Log.e(TAG, getString(R.string.exception_tango_error), e);
                } catch (SecurityException e) {
                    Log.e(TAG, getString(R.string.permission_motion_tracking), e);
                }
                try {
                    mTango.connect(mConfig);
                } catch (TangoOutOfDateException e) {
                    Log.e(TAG, getString(R.string.exception_out_of_date), e);
                } catch (TangoErrorException e) {
                    Log.e(TAG, getString(R.string.exception_tango_error), e);
                }
            }
        });
    }


tangoの設定
depthをonに

    /**
     * Sets up the tango configuration object. Make sure mTango object is initialized before
     * making this call.
     */
    private TangoConfig setupTangoConfig(Tango tango) {
        // Create a new Tango Configuration and enable the Depth Sensing API
        TangoConfig config = new TangoConfig();
        config = tango.getConfig(config.CONFIG_TYPE_DEFAULT);
        config.putBoolean(TangoConfig.KEY_BOOLEAN_DEPTH, true);
        return config;
    }

tangolistenerの設定

    /**
     * Set up the callback listeners for the Tango service, then begin using the Motion
     * Tracking API. This is called in response to the user clicking the 'Start' Button.
     */
    private void setTangoListeners() {
        // Lock configuration and connect to Tango
        // Select coordinate frame pair
        final ArrayList<TangoCoordinateFramePair> framePairs =
                new ArrayList<TangoCoordinateFramePair>();
        framePairs.add(new TangoCoordinateFramePair(
                TangoPoseData.COORDINATE_FRAME_START_OF_SERVICE,
                TangoPoseData.COORDINATE_FRAME_DEVICE));

        // Listen for new Tango data
        mTango.connectListener(framePairs, new OnTangoUpdateListener() {
            @Override
            public void onPoseAvailable(final TangoPoseData pose) {
                // We are not using TangoPoseData for this application
            }

            @Override
            public void onXyzIjAvailable(final TangoXyzIjData xyzIjData) {
                logXyzIj(xyzIjData);
            }

            @Override
            public void onTangoEvent(final TangoEvent event) {
            }

            @Override
            public void onFrameAvailable(int cameraId) {
                // We are not using onFrameAvailable for this application
            }
        });
    }

onXyzIjAvailableでdepthsensorからの座標を取得する
TangoXyzIjData  |  Tango Java API  |  Google Developers

取得したデータは以下の感じで使う

    /**
     * Log the point count and the average depth of the given XyzIj data
     * in the Logcat as information.
     */
    private void logXyzIj(TangoXyzIjData xyzIjData) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("Point count: " + xyzIjData.xyzCount);
        stringBuilder.append(". Average depth (m): " + calculateAveragedDepth(xyzIjData.xyz));
        Log.i(TAG, stringBuilder.toString());
    }

    /**
     * Calculates the average depth from a point cloud buffer.
     */
    private float calculateAveragedDepth(FloatBuffer pointCloudBuffer) {
        int pointCount = pointCloudBuffer.capacity() / 3;
        float totalZ = 0;
        float averageZ = 0;
        for (int i = 0; i < pointCloudBuffer.capacity() - 3; i = i + 3) {
            totalZ = totalZ + pointCloudBuffer.get(i + 2);
        }
        if (pointCount != 0) {
            averageZ = totalZ / pointCount;
        }
        return averageZ;
    }

感想

実機が無いので試せないのが辛いが大体の流れはわかった
どのセンサーを使うか登録してlistenerで取得する流れ

後はtango conceptが少しコツになるくらいので眺めておくと吉
Tango Concepts  |  Tango  |  Google Developers

広告を非表示にする