使用深度信息时,Dark Google Tango 相机表面

Dark Google Tango camera surface when using depth information

情况: 我正在尝试在 Java 中编写一个 Google Tango 应用程序,允许用户看到带有虚拟对象的探戈相机在顶部(即视频透视增强现实视图)并使用 Tango depth/point 云信息。

问题: 每当我尝试在 Tango 上启用深度传感器时,相机图像变得非常暗。当我禁用深度感应时,一切正常。以下是一些屏幕截图:

Google 启用深度信息的 Tango:

mConfig.putBoolean(TangoConfig.KEY_BOOLEAN_DEPTH, true);

禁用深度信息的同一应用程序:

mConfig.putBoolean(TangoConfig.KEY_BOOLEAN_DEPTH, false);

问题:如何获得干净的相机图像并启用 Tango 的深度信息?如果纯色不行,能不能搞个高反差B/W?我怀疑这是一个同步问题,也许表面是在 depth/point 云算法扰动图像之后绘制的。或者,相机格式更改为支持深度感应,不适合预览。

我正在使用 helpful and related post

中建议的 Tango.setSurface 技术

我故意不使用 Android 的原生相机 API。

(编辑: 这个post是基于费马更新的。高斯更新后还没有确认)

我的主要 activity 代码在下面 post 编辑。完整项目位于 this github repo

提前致谢!

/*
 * Copyright 2014 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.digitalblacksmith.tango_ar_pointcloud;

import com.google.atap.tangoservice.Tango;
import com.google.atap.tangoservice.Tango.OnTangoUpdateListener;
import com.google.atap.tangoservice.TangoConfig;
import com.google.atap.tangoservice.TangoCoordinateFramePair;
import com.google.atap.tangoservice.TangoErrorException;
import com.google.atap.tangoservice.TangoEvent;
import com.google.atap.tangoservice.TangoInvalidException;
import com.google.atap.tangoservice.TangoOutOfDateException;
import com.google.atap.tangoservice.TangoPoseData;
import com.google.atap.tangoservice.TangoXyzIjData;

import android.app.Activity;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import java.io.FileInputStream;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.ArrayList;

/**
 * 
 * Modified Main Activity class from the Original Google Tango SDK  Motion Tracking API Sample. 
 * 
 * Creates a GLSurfaceView for the OpenGL scene, which displays a cube
 * Then adds a SurfaceView for the camera image.  The surface is connected 
 * to the Tango camera.  This is necessary if one wants to get point cloud
 * data from the Tango AND use the camera for video-see through Augmented Reality.
 * 
 * Lessons learned:  Ensure your onPause and onResume actions are handled correctly
 * in terms of disconnecting and reconnecting the Tango!!  If the Tango is not
 * disconnected and reconnected properly, you will get a black background and
 * may think the issue is something else.
 * 
 * @author  Steve Henderson @stevehenderson 
 * 
 */
public class PointCloudActivity extends Activity implements View.OnClickListener, SurfaceHolder.Callback  {

    private static final String TAG = PointCloudActivity.class.getSimpleName();
    private static final int SECS_TO_MILLISECS = 1000;


    private Tango mTango;
    private TangoConfig mConfig;
    private TextView mDeltaTextView;
    private TextView mPoseCountTextView;
    private TextView mPoseTextView;
    private TextView mQuatTextView;
    private TextView mPoseStatusTextView;
    private TextView mTangoServiceVersionTextView;
    private TextView mApplicationVersionTextView;
    private TextView mTangoEventTextView;
    private TextView mPointCountTextView;
    private TextView mAverageZTextView;
    private TextView mFrequencyTextView;
    private float mPreviousTimeStamp;
    private int mPreviousPoseStatus;
    private int count;
    private float mDeltaTime;
    private Button mMotionResetButton;
    private Button mDropBoxButton;
    //private boolean mIsAutoRecovery;

    //private PCRenderer mOpenGL2Renderer;
    private OpenGL2PointCloudRenderer mOpenGL2Renderer;
    private DemoRenderer mDemoRenderer;
    private GLSurfaceView mGLView;

    private SurfaceView surfaceView;

    private float mXyIjPreviousTimeStamp;
    private float mCurrentTimeStamp;

    boolean first_initialized = false;

    Surface tangoSurface;

    Vector3f lastPosition;
    Vector3f dropBoxPosition;


    /**
     * Set up the activity using OpenGL 20
     */
    @SuppressWarnings("deprecation")
    private void setUpOpenGL20() {

        ///////////////////////
        //Create GLSurface
        ///////////////////////
        // OpenGL view where all of the graphics are drawn
        mGLView = new GLSurfaceView(this);
        mGLView.setEGLContextClientVersion(2);
        mGLView.setEGLConfigChooser(8,8,8,8,16,0);
        SurfaceHolder glSurfaceHolder = mGLView.getHolder();
        glSurfaceHolder.setFormat(PixelFormat.TRANSLUCENT);

        ////////////////////////////////////
        // Instantiate the Tango service
        ///////////////////////////////////
        mTango = new Tango(this);
        // Create a new Tango Configuration and enable the MotionTrackingActivity API
        mConfig = new TangoConfig();
        mConfig = mTango.getConfig(TangoConfig.CONFIG_TYPE_CURRENT);
        mConfig.putBoolean(TangoConfig.KEY_BOOLEAN_MOTIONTRACKING, true);
        ///  --->If the next property is false (disabled depth) then image ok <-------
        mConfig.putBoolean(TangoConfig.KEY_BOOLEAN_DEPTH, true);


        // Configure OpenGL renderer
        //mRenderer = new GLClearRenderer();
        int maxDepthPoints = mConfig.getInt("max_point_cloud_elements");

        mOpenGL2Renderer = new OpenGL2PointCloudRenderer(maxDepthPoints);

        mDemoRenderer = mOpenGL2Renderer;
        mOpenGL2Renderer.setFirstPersonView();
        mGLView.setRenderer(mOpenGL2Renderer);
        mGLView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
        //setContentView(mGLView);


        try {
            setTangoListeners();
        } catch (TangoErrorException e) {
            Toast.makeText(getApplicationContext(), R.string.TangoError, Toast.LENGTH_SHORT).show();
        } catch (SecurityException e) {
            Toast.makeText(getApplicationContext(), R.string.motiontrackingpermission,
                    Toast.LENGTH_SHORT).show();
        }

        //////////////////////////
        // Create Camera Surface
        //////////////////////////
        surfaceView = new SurfaceView(this);
        SurfaceHolder activitySurfaceHolder = surfaceView.getHolder();
        activitySurfaceHolder.addCallback(this);


        //mGLView.setZOrderOnTop(true);
        setContentView(mGLView);
        addContentView( surfaceView, new LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT ) );

        /////////////////////////
        //Create UI Objects 
        ////////////////////////
        LayoutInflater inflater = getLayoutInflater();
        View tmpView;
        tmpView = inflater.inflate(R.layout.activity_motion_tracking, null);
        getWindow().addContentView(tmpView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
                ViewGroup.LayoutParams.FILL_PARENT)); 


        mApplicationVersionTextView = (TextView) findViewById(R.id.appversion);

        mApplicationVersionTextView.setText("OpenGL 2.0");

        // Button to reset motion tracking
        mMotionResetButton = (Button) findViewById(R.id.resetmotion);
        // Set up button click listeners
        mMotionResetButton.setOnClickListener(this);

        // Button to drop position box (breadcrumb cube)
        mDropBoxButton = (Button) findViewById(R.id.dropbox);
        // Set up button click listeners
        mDropBoxButton.setOnClickListener(this);

        //mOpenGL2Renderer.setFirstPersonView();

    }


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Intent intent = getIntent();
        setUpOpenGL20();

        // Text views for displaying translation and rotation data
        mPoseTextView = (TextView) findViewById(R.id.pose);
        mQuatTextView = (TextView) findViewById(R.id.quat);
        mPoseCountTextView = (TextView) findViewById(R.id.posecount);
        mDeltaTextView = (TextView) findViewById(R.id.deltatime);
        mTangoEventTextView = (TextView) findViewById(R.id.tangoevent);
        mPointCountTextView = (TextView) findViewById(R.id.pointCount);
        mAverageZTextView = (TextView) findViewById(R.id.averageZ);
        mFrequencyTextView = (TextView) findViewById(R.id.frameDelta);

        // Text views for the status of the pose data and Tango library versions
        mPoseStatusTextView = (TextView) findViewById(R.id.status);
        mTangoServiceVersionTextView = (TextView) findViewById(R.id.version);

        // Display the library version for debug purposes
        mTangoServiceVersionTextView.setText(mConfig.getString("tango_service_library_version"));

        dropBoxPosition = new Vector3f();
        lastPosition = new Vector3f();
    }

    private void motionReset() {

        mTango.resetMotionTracking();
    }

    private void dropBox() {
        dropBoxPosition.setTo(lastPosition);
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.i(TAG, "OnPause");
        try {
            mTango.disconnect();
            Log.i(TAG,"Pausing..TANGO disconnected");
        } catch (TangoErrorException e) {
            Toast.makeText(getApplicationContext(), R.string.TangoError, Toast.LENGTH_SHORT).show();
        }

    }

    protected void onResume() {
        super.onResume();
        Log.i(TAG, "OnResume");

        try {
            //setTangoListeners();
        } catch (TangoErrorException e) {
            Log.e(TAG,e.toString());
        } catch (SecurityException e) {
            Log.e(TAG,e.toString());
        }
        try {           
            if(first_initialized)mTango.connect(mConfig);
        } catch (TangoOutOfDateException e) {
            Log.e(TAG,e.toString());
        } catch (TangoErrorException e) {
            Log.e(TAG,e.toString());
        }
        try {
            //setUpExtrinsics();
        } catch (TangoErrorException e) {
            Log.e(TAG,e.toString());
        } catch (SecurityException e) {
            Log.e(TAG,e.toString());
        }

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.resetmotion:
            motionReset();
            break;
        case R.id.dropbox:
            dropBox();
            break;
        default:
            Log.w(TAG, "Unknown button click");
            return;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        return false; 
    }


    /**
     * Set up the TangoConfig and the 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) {
                // Log whenever Motion Tracking enters a n invalid state
                if (pose.statusCode == TangoPoseData.POSE_INVALID) {
                    Log.w(TAG, "Invalid State");
                }
                if (mPreviousPoseStatus != pose.statusCode) {
                    count = 0;
                }
                count++;
                mPreviousPoseStatus = pose.statusCode;
                mDeltaTime = (float) (pose.timestamp - mPreviousTimeStamp) * SECS_TO_MILLISECS;
                mPreviousTimeStamp = (float) pose.timestamp;
                // Update the OpenGL renderable objects with the new Tango Pose
                // data
                float[] translation = pose.getTranslationAsFloats();

                mGLView.requestRender();

                // Update the UI with TangoPose information
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        DecimalFormat threeDec = new DecimalFormat("0.000");
                        String translationString = "[" + threeDec.format(pose.translation[0])
                                + ", " + threeDec.format(pose.translation[1]) + ", "
                                + threeDec.format(pose.translation[2]) + "] ";
                        String quaternionString = "[" + threeDec.format(pose.rotation[0]) + ", "
                                + threeDec.format(pose.rotation[1]) + ", "
                                + threeDec.format(pose.rotation[2]) + ", "
                                + threeDec.format(pose.rotation[3]) + "] ";

                        float x = (float) pose.translation[0];
                        float y = (float) pose.translation[1];
                        float z = (float) pose.translation[2];

                        mDemoRenderer.setCameraPosition(x-dropBoxPosition.x, y-dropBoxPosition.y, z-dropBoxPosition.z);

                        lastPosition.setTo(x, y, z);

                        float qx = (float) pose.rotation[0];
                        float qy = (float) pose.rotation[1];
                        float qz = (float) pose.rotation[2];
                        float qw = (float) pose.rotation[3];

                        mDemoRenderer.setCameraAngles(qx, qy, qz, qw);

                        // Display pose data on screen in TextViews
                        //Log.i(TAG,translationString);
                        mPoseTextView.setText(translationString);
                        mQuatTextView.setText(quaternionString);
                        mPoseCountTextView.setText(Integer.toString(count));
                        mDeltaTextView.setText(threeDec.format(mDeltaTime));
                        if (pose.statusCode == TangoPoseData.POSE_VALID) {
                            mPoseStatusTextView.setText(R.string.pose_valid);
                        } else if (pose.statusCode == TangoPoseData.POSE_INVALID) {
                            mPoseStatusTextView.setText(R.string.pose_invalid);
                        } else if (pose.statusCode == TangoPoseData.POSE_INITIALIZING) {
                            mPoseStatusTextView.setText(R.string.pose_initializing);
                        } else if (pose.statusCode == TangoPoseData.POSE_UNKNOWN) {
                            mPoseStatusTextView.setText(R.string.pose_unknown);
                        }
                    }
                });
            }

            @Override
            public void onXyzIjAvailable(final TangoXyzIjData xyzIj) {
                //Log.i(TAG,"xyzijAvailable!!!!!!!!");
                mCurrentTimeStamp = (float) xyzIj.timestamp;
                final float frameDelta = (mCurrentTimeStamp - mXyIjPreviousTimeStamp)
                        * SECS_TO_MILLISECS;
                mXyIjPreviousTimeStamp = mCurrentTimeStamp;
                byte[] buffer = new byte[xyzIj.xyzCount * 3 * 4];
                //////mGLView.requestRender();
                FileInputStream fileStream = new FileInputStream(
                        xyzIj.xyzParcelFileDescriptor.getFileDescriptor());
                try {
                    fileStream.read(buffer,
                            xyzIj.xyzParcelFileDescriptorOffset, buffer.length);
                    fileStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    TangoPoseData pointCloudPose = mTango.getPoseAtTime(
                            mCurrentTimeStamp, framePairs.get(0));

                    mOpenGL2Renderer.getPointCloud().UpdatePoints(buffer,
                            xyzIj.xyzCount);
                    mOpenGL2Renderer.getModelMatCalculator()
                    .updatePointCloudModelMatrix(
                            pointCloudPose.getTranslationAsFloats(),
                            pointCloudPose.getRotationAsFloats());
                    mOpenGL2Renderer.getPointCloud().setModelMatrix(
                            mOpenGL2Renderer.getModelMatCalculator()
                            .getPointCloudModelMatrixCopy());
                } catch (TangoErrorException e) {
                    Toast.makeText(getApplicationContext(),
                            R.string.TangoError, Toast.LENGTH_SHORT).show();
                } catch (TangoInvalidException e) {
                    Toast.makeText(getApplicationContext(),
                            R.string.TangoError, Toast.LENGTH_SHORT).show();
                }

                // Must run UI changes on the UI thread. Running in the Tango
                // service thread
                // will result in an error.
                runOnUiThread(new Runnable() {
                    DecimalFormat threeDec = new DecimalFormat("0.000");

                    @Override
                    public void run() {
                        // Display number of points in the point cloud
                        mPointCountTextView.setText(Integer
                                .toString(xyzIj.xyzCount));
                        mFrequencyTextView.setText(""
                                + threeDec.format(frameDelta));
                        mAverageZTextView.setText(""
                                + threeDec.format(mOpenGL2Renderer.getPointCloud()
                                        .getAverageZ()));
                    }
                });
            }

            @Override
            public void onTangoEvent(final TangoEvent event) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        mTangoEventTextView.setText(event.eventKey + ": " + event.eventValue);
                    }
                });
            }
        });
    }


    private void setUpExtrinsics() {
        // Get device to imu matrix.
        TangoPoseData device2IMUPose = new TangoPoseData();
        TangoCoordinateFramePair framePair = new TangoCoordinateFramePair();
        framePair.baseFrame = TangoPoseData.COORDINATE_FRAME_IMU;
        framePair.targetFrame = TangoPoseData.COORDINATE_FRAME_DEVICE;
        device2IMUPose = mTango.getPoseAtTime(0.0, framePair);
        // mRenderer.getModelMatCalculator().SetDevice2IMUMatrix(
        //         device2IMUPose.getTranslationAsFloats(), device2IMUPose.getRotationAsFloats());

        // Get color camera to imu matrix.
        TangoPoseData color2IMUPose = new TangoPoseData();
        framePair.baseFrame = TangoPoseData.COORDINATE_FRAME_IMU;
        framePair.targetFrame = TangoPoseData.COORDINATE_FRAME_CAMERA_COLOR;
        color2IMUPose = mTango.getPoseAtTime(0.0, framePair);

        // mRenderer.getModelMatCalculator().SetColorCamera2IMUMatrix(
        //        color2IMUPose.getTranslationAsFloats(), color2IMUPose.getRotationAsFloats());
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {

        Surface surface = holder.getSurface();

        if (surface.isValid()) {

            mTango.connectSurface(0, surface);
            first_initialized=true;
            mTango.connect(mConfig);

        }

    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
            int height) {
        // TODO Auto-generated method stub

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        mTango.disconnectSurface(0);

    }

}

在 CGUI 的朋友的帮助下,我发现了某种解决方案。

在我上面的 post 中,您会注意到启用深度相机的图像存在曝光不足错误。看起来 Tango 正在对相机图像进行一些自动曝光。

当我在白天尝试时,自然光很好,加上一些泛光灯,我收到了更好的效果:

启用的深度:

深度禁用:

因此,在使用颜色和深度时,一种可能 workaround/consideration 是仔细管理环境中的光线。考虑到我在调用 Tango 演示应用程序中看到的一些校准例程,这是有道理的"daylight"

更新 你也可以 select B/W 鱼眼相机,如果你不介意缺少颜色和失真:

 mTango.connectSurface(2, surface);  //0-->color cam; 2--> B/W fisheye