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中两个节点之间的距离以米为单位计算。

