Android ARCore –与相机的距离
在本教程中,我们将讨论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中两个节点之间的距离以米为单位计算。