Android ARCore –与相机的距离

时间:2020-02-23 14:28:45  来源:igfitidea点击:

在本教程中,我们将讨论Google推出的ARCore。
我们将精通ARCore的基础知识。
之后,我们将开发一个Android应用程序以获取节点对象与相机之间的距离。
前面我们已经在ARCore中介绍了Hello World示例。
现在开始吧!

ARCore基础

增强现实基本上可以使我们在现实环境中显示虚拟对象,并允许与它们进行交互。

ARCore要求:

  • Android N +
  • OpenGL 3.0以上

无需引入OpenGL 3d图形编程知识,因为为此引入了Sceneform SDK。

Sceneform允许我们创建3d模型。
它由ShapeFactory和MaterialFactory类组成,这些类允许我们从简单的形状和材料创建可渲染的对象。
我们可以在本教程的后面部分轻松地创建球体,立方体,圆柱体等形状。

" ARFragment"用于创建Ar场景和AR会话(两者都是自动创建的)。
使用" ARFragment",我们可以检测平面或者特征点。

在ArFragment上设置了setOnTapArPlaneListener,以在单击事件发生时侦听更改。

以下列出了构成ARCore核心的几个术语,并提供了描述:

  • 场景–这是渲染3D对象的地方。

  • HitResult –点击后,可为我们提供真实对象的位置。
    基本上,它通过找到与来自无限远的虚构光线的第一个相交点来估计位置。

  • 锚点–根据3D空间中x,y,z坐标在现实世界中的固定位置。
    就像船锚

  • 姿势–提供场景中对象的位置和方向。

  • AnchorNode –这是自动将自己放置在世界上的节点。
    这是检测到平面时设置的第一个节点。

  • TransformableNode –此节点是我们设置3d对象的位置。
    它可以根据用户交互进行交互,缩放,转换和旋转。

在下一部分中,我们将开发AR应用程序,在该应用程序中,我们将计算物体与我们(即相机)之间的距离。

怎么做?一旦将物体放在飞机上,我们就知道它是一个姿势。
随着场景的变化,框架会更新。
从框架中,我们可以获得相机的姿势。
现在我们有了两个姿势,它们之间的距离只是一个数学公式!

项目结构

Android Arcore项目结构

将以下依赖项添加到build.gradle中:

implementation 'com.google.ar.sceneform.ux:sceneform-ux:1.10.0'

在AndroidManifest文件中,我们需要添加以下内容:

<uses-permission android:name="android.permission.CAMERA" 
  <uses-feature
      android:name="android.hardware.camera.ar"
      android:required="true" 

在application标记内添加以下内容:

<meta-data
              android:name="com.google.ar.core"
              android:value="required" 

下面给出了" activity_main.xml"的代码:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="https://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <fragment
      android:id="@+id/ux_fragment"
      android:name="com.google.ar.sceneform.ux.ArFragment"
      android:layout_width="match_parent"
      android:layout_height="match_parent" 

  <TextView
      android:id="@+id/tvDistance"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_gravity="bottom"
      android:background="@android:color/black"
      android:gravity="center"
      android:padding="8dp"
      android:text="Distance from camera"
      android:textColor="@android:color/white"
      android:textSize="20sp" 

</FrameLayout>

MainActivity.java的代码如下:

package com.theitroad.androidarcoredistancecamera;

import androidx.appcompat.app.AppCompatActivity;

import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;

import com.google.ar.core.Anchor;
import com.google.ar.core.Frame;
import com.google.ar.core.Pose;
import com.google.ar.sceneform.AnchorNode;
import com.google.ar.sceneform.FrameTime;
import com.google.ar.sceneform.Scene;
import com.google.ar.sceneform.math.Vector3;
import com.google.ar.sceneform.rendering.Color;
import com.google.ar.sceneform.rendering.MaterialFactory;
import com.google.ar.sceneform.rendering.ModelRenderable;
import com.google.ar.sceneform.rendering.ShapeFactory;
import com.google.ar.sceneform.ux.ArFragment;
import com.google.ar.sceneform.ux.TransformableNode;

import java.util.Objects;

public class MainActivity extends AppCompatActivity implements Scene.OnUpdateListener {

  private static final double MIN_OPENGL_VERSION = 3.0;
  private static final String TAG = MainActivity.class.getSimpleName();

  private ArFragment arFragment;
  private AnchorNode currentAnchorNode;
  private TextView tvDistance;
  ModelRenderable cubeRenderable;
  private Anchor currentAnchor = null;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);

      if (!checkIsSupportedDeviceOrFinish(this)) {
          Toast.makeText(getApplicationContext(), "Device 不支持", Toast.LENGTH_LONG).show();
      }

      setContentView(R.layout.activity_main);

      arFragment = (ArFragment) getSupportFragmentManager().findFragmentById(R.id.ux_fragment);
      tvDistance = findViewById(R.id.tvDistance);

      initModel();

      arFragment.setOnTapArPlaneListener((hitResult, plane, motionEvent) -> {
          if (cubeRenderable == null)
              return;

          //Creating Anchor.
          Anchor anchor = hitResult.createAnchor();
          AnchorNode anchorNode = new AnchorNode(anchor);
          anchorNode.setParent(arFragment.getArSceneView().getScene());

          clearAnchor();

          currentAnchor = anchor;
          currentAnchorNode = anchorNode;

          TransformableNode node = new TransformableNode(arFragment.getTransformationSystem());
          node.setRenderable(cubeRenderable);
          node.setParent(anchorNode);
          arFragment.getArSceneView().getScene().addOnUpdateListener(this);
          arFragment.getArSceneView().getScene().addChild(anchorNode);
          node.select();

      });

  }

  public boolean checkIsSupportedDeviceOrFinish(final Activity activity) {

      String openGlVersionString =
              ((ActivityManager) Objects.requireNonNull(activity.getSystemService(Context.ACTIVITY_SERVICE)))
                      .getDeviceConfigurationInfo()
                      .getGlEsVersion();
      if (Double.parseDouble(openGlVersionString) < MIN_OPENGL_VERSION) {
          Log.e(TAG, "Sceneform requires OpenGL ES 3.0 later");
          Toast.makeText(activity, "Sceneform requires OpenGL ES 3.0 or later", Toast.LENGTH_LONG)
                  .show();
          activity.finish();
          return false;
      }
      return true;
  }

  private void initModel() {
      MaterialFactory.makeTransparentWithColor(this, new Color(android.graphics.Color.RED))
              .thenAccept(
                      material -> {
                          Vector3 vector3 = new Vector3(0.05f, 0.01f, 0.01f);
                          cubeRenderable = ShapeFactory.makeCube(vector3, Vector3.zero(), material);
                          cubeRenderable.setShadowCaster(false);
                          cubeRenderable.setShadowReceiver(false);
                      });
  }

  private void clearAnchor() {
      currentAnchor = null;

      if (currentAnchorNode != null) {
          arFragment.getArSceneView().getScene().removeChild(currentAnchorNode);
          currentAnchorNode.getAnchor().detach();
          currentAnchorNode.setParent(null);
          currentAnchorNode = null;
      }
  }

  @Override
  public void onUpdate(FrameTime frameTime) {
      Frame frame = arFragment.getArSceneView().getArFrame();

      Log.d("API123", "onUpdateframe... current anchor node " + (currentAnchorNode == null));

      if (currentAnchorNode != null) {
          Pose objectPose = currentAnchor.getPose();
          Pose cameraPose = frame.getCamera().getPose();

          float dx = objectPose.tx() - cameraPose.tx();
          float dy = objectPose.ty() - cameraPose.ty();
          float dz = objectPose.tz() - cameraPose.tz();

          ///Compute the straight-line distance.
          float distanceMeters = (float) Math.sqrt(dx * dx + dy * dy + dz * dz);
          tvDistance.setText("Distance from camera: " + distanceMeters + " metres");

          /*float[] distance_vector = currentAnchor.getPose().inverse()
                  .compose(cameraPose).getTranslation();
          float totalDistanceSquared = 0;
          for (int i = 0; i < 3; ++i)
              totalDistanceSquared += distance_vector[i] * distance_vector[i];*/
      }
  }
}

在上面的代码中,我们执行以下操作:

  • 检查手机是否兼容AR。

  • 创建一个3d立方体形状的模型。

  • 一旦检测到飞机,点击添加。

  • 在每帧中更新从相机到锚点的距离。

  • 再次点击时,清除上一个锚点。

我们可以在可变形节点上以及node.setOnTapListener {}上设置点击监听器,并在单击该节点时对其进行填充(播放声音等)。

实际应用程序的输出如下:

Android Arcore距离相机输出

如您所见,当我们将摄像机移近放置在场景中的立方体时,距离会发生变化。

ARCore中两个节点之间的距离以米为单位计算。