ARCore 构建增强图像应用程序,识别图片加载模型
在本教程中,您将学习如何通过将锚点设置为特定场景而不是常规平面来将3D模型放置在现实世界中。通过Google的ARCore,您可以增加可以被arcore识别的2D图像,然后将3D模型放在它们上面。
您提供一些参考图像,并且ARCore跟踪确定这些图像在环境中的物理位置。增强图像已经广泛使用,如书籍,报纸,杂志等。
您将对ARCore和Sceneform中的一些术语有基本的了解,例如Scene,Anchor,Node,TransformableNode等。
什么是增强图像?
ARCore中的增强图像允许您构建可在用户环境中响应2D图像(例如海报或产品包装)的AR应用程序。您提供了一组参考图像,一旦在摄像机视图中检测到这些图像,ARCore跟踪就会告诉您这些图像在AR会话中的物理位置。
基本上,使用增强图像,您可以将简单的2D图像转换为增强图像,该图像可以被您的应用程序识别,然后用于在其上方放置3D模型。
当您可能想要使用增强图像?
以下是使用增强图像之前可能需要考虑的一些限制:
- 您的使用案例不得涉及同时扫描20多张图像。由于ARCore一次只能跟踪多达20张图像。
- 现实世界中物理对应物的尺寸必须大于15cm X 15cm且平坦。
- 您不想跟踪移动的对象。尽管可以在图像停止后开始跟踪,但ARCore无法跟踪运动图像。
- ARCore使用参考图像中的特征点,可以存储多达1000个图像的特征点信息。
选择一个好的参考图像
以下是一些选择良好参考图像以提高arcore可检测性的技巧:
- 增强图像支持PNG,JPEG和JPG格式。
- 检测基于高对比度的点,因此无论是使用彩色还是黑/白参考图像,都检测彩色和黑/白图像。
- 图像的分辨率必须至少为300 X 300像素。
- 使用高分辨率图像并不意味着提高性能。
- 必须避免使用具有重复特征的图像,如图案,波尔卡圆点。
- 使用arcoreimg工具评估参考图像的好坏程度。建议得分至少为75分。
如何使用 arcoreimg工具:
从这个链接下载arcore sdk for android:
在任何您喜欢的地方解压缩zip文件的zip内容。
导航到解压缩的文件夹,然后转到工具 - > arcoreimg - > windows(linux / macos,无论你使用什么)
在此位置打开命令提示符。
现在输入以下命令:
// 将dog.png替换为图像的完整路径。
arcoreimg.exe eval-img --input_image_path=dog.png
开始使用增强图像应用程序
现在你已经熟悉了ARCore和Sceneform,并选择了一个分数为75+的好参考图像,现在是时候开始编写应用程序!
编写代码
将创建一个自定义片段以添加到界面中。需要一个自定义代码,将改变默认代码的一些属性。
创建一个名为“CustomArFragment”的类,并从ArFragment扩展它。以下是CustomArFragment的代码:
package com.ayusch.augmentedimages;
import android.util.Log;
import com.google.ar.core.Config;
import com.google.ar.core.Session;
import com.google.ar.sceneform.ux.ArFragment;
public class CustomArFragment extends ArFragment {
@Override
protected Config getSessionConfiguration(Session session) {
getPlaneDiscoveryController().setInstructionView(null);
Config config = new Config(session);
config.setUpdateMode(Config.UpdateMode.LATEST_CAMERA_IMAGE);
session.configure(config);
getArSceneView().setupSession(session);
return config;
}
}
首先,我们将平面发现指令设置为null。通过这样做,我们关闭在初始化片段之后出现的手形图标,该图标指示用户移动他的手机。我们不再需要它,因为我们没有检测随机平面而是特定图像。
接下来,我们将会话的更新模式设置为LATEST_CAMERA_IMAGE。这可确保在相机帧更新时调用更新侦听器。它配置更新方法的行为。
设置增强图像数据库
在assets文件夹中添加您选择的参考图像(您要在物理世界中检测)。如果您的资源文件夹不存在,请创建一个。 现在我们将添加增强图像到我们的数据库,然后在现实世界中检测到。
我们将在创建片段(场景)后立即设置此数据库。然后我们检查此调用的成功与否,并相应地设置日志。将以下代码添加到自定义片段:
if ((((MainActivity) getActivity()).setupAugmentedImagesDb(config, session))) {
Log.d("SetupAugImgDb", "Success");
} else {
Log.e("SetupAugImgDb","Faliure setting up db");
}
这是CustomArFragment的代码:
package com.ayusch.augmentedimages;
import android.util.Log;
import com.google.ar.core.Config;
import com.google.ar.core.Session;
import com.google.ar.sceneform.ux.ArFragment;
public class CustomArFragment extends ArFragment {
@Override
protected Config getSessionConfiguration(Session session) {
getPlaneDiscoveryController().setInstructionView(null);
Config config = new Config(session);
config.setUpdateMode(Config.UpdateMode.LATEST_CAMERA_IMAGE);
session.configure(config);
getArSceneView().setupSession(session);
if ((((MainActivity) getActivity()).setupAugmentedImagesDb(config, session))) {
Log.d("SetupAugImgDb", "Success");
} else {
Log.e("SetupAugImgDb","Faliure setting up db");
}
return config;
}
我们很快将 在MainActivity中创建setupAugmentedImagesDb方法。现在创建了CustomArFragment,让我们将它添加到activity_main.xml,这里是您的activity_main.xml的代码:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<fragment
android:id="@+id/sceneform_fragment"
android:name="com.google.ar.sceneform.samples.augmentedimage.CustomArFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
请注意,我们将此片段的名称设置为CustomArFragment。这是确保添加的片段是我们的自定义片段所必需的。这将确保处理权限处理和会话初始化。
将图像添加到增强图像数据库
在这里,我们将设置我们的图像数据库,在现实世界中找到参考图像,然后相应地添加3D模型。
让我们从设置数据库开始。在MainActivity.java类中创建一个公共函数setupAugmentedImagesDb:
public boolean setupAugmentedImagesDb(Config config, Session session) {
AugmentedImageDatabase augmentedImageDatabase;
Bitmap bitmap = loadAugmentedImage();
if (bitmap == null) {
return false;
}
augmentedImageDatabase = new AugmentedImageDatabase(session);
augmentedImageDatabase.addImage("tiger", bitmap);
config.setAugmentedImageDatabase(augmentedImageDatabase);
return true;
}
private Bitmap loadAugmentedImage() {
try (InputStream is = getAssets().open("default.jpg")) {
return BitmapFactory.decodeStream(is);
} catch (IOException e) {
Log.e("ImageLoad", "IO Exception", e);
}
return null;
}
我们还有loadAugmentedImage方法,它从assets文件夹加载图像并返回一个位图。
在setupAugmentedImagesDb中,我们首先为此会话初始化数据库,然后将图像添加到此数据库。我们将我们的形象命名为“老虎”。然后我们为此会话配置设置数据库并返回true,表示图像已成功添加。
default.jpg识别图, frame_lower_left.sfb模型文件检测现实世界中的参考图像
现在我们将开始在现实世界中检测我们的参考图像。为了做到这一点,我们将为场景添加一个监听器,每次创建一个帧时都会调用该监听器,并且将分析该帧以查找我们的参考图像。
在MainActivity.java 的onCreate方法中添加此行
arFragment.getArSceneView().getScene().addOnUpdateListener(this::onUpdateFrame);
现在将onUpdateFrame方法添加到MainActivity:
@RequiresApi(api = Build.VERSION_CODES.N)
private void onUpdateFrame(FrameTime frameTime) {
Frame frame = arFragment.getArSceneView().getArFrame();
Collection<AugmentedImage> augmentedImages = frame.getUpdatedTrackables(AugmentedImage.class);
for (AugmentedImage augmentedImage : augmentedImages) {
if (augmentedImage.getTrackingState() == TrackingState.TRACKING) {
if (augmentedImage.getName().equals("tiger") && shouldAddModel) {
placeObject(arFragment, augmentedImage.createAnchor(augmentedImage.getCenterPose()), Uri.parse("models/frame_upper_left.sfb"));
shouldAddModel = false;
}
}
}
}
在第一行中,我们从场景中获取帧。甲帧可以被想象为在视频的中间的快照。如果您熟悉视频的工作原理,您可能会知道它们是一系列静止图像,一个接一个地快速翻转,给人以电影的印象。我们正在提取其中一张照片。
一旦我们有了框架,我们就会分析我们的参考图像。我们使用frame.getUpdatedTrackables提取了ARCore跟踪的所有项目的列表。这是所有检测到的图像的集合。然后我们循环收集并检查框架中是否存在我们的图像“tiger”。
如果我们找到匹配项,那么我们继续并在检测到的图像上放置3D模型。
注意:我已添加,shouldAddModel 以确保我们只添加一次模型。
将3D模型放在参考图像上
现在我们已经在现实世界中检测到了我们的图像,我们可以开始在其上添加3D模型。我们将从之前的项目中复制placeObject和addNodeToScene方法,并在此处添加它们。
虽然我之前已经逐行解释了这些方法的作用,但这里有一个概述:
- PlaceObject:此方法用于从提供的Uri构建可渲染。构建可渲染后,将其传递到addNodeToScene方法,其中可渲染附加到节点,并将该节点放置到场景上。
- AddNodeToScene:此方法从接收到的锚点创建AnchorNode,创建附加了可渲染项的另一个节点,然后将此节点添加到AnchorNode并将AnchorNode添加到场景中。
这是我们最终的MainActivity.java类:
package com.ayusch.augmentedimages;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import com.google.ar.core.Anchor;
import com.google.ar.core.AugmentedImage;
import com.google.ar.core.AugmentedImageDatabase;
import com.google.ar.core.Config;
import com.google.ar.core.Frame;
import com.google.ar.core.Session;
import com.google.ar.core.TrackingState;
import com.google.ar.sceneform.AnchorNode;
import com.google.ar.sceneform.FrameTime;
import com.google.ar.sceneform.rendering.ModelRenderable;
import com.google.ar.sceneform.rendering.Renderable;
import com.google.ar.sceneform.ux.ArFragment;
import com.google.ar.sceneform.ux.TransformableNode;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
public class MainActivity extends AppCompatActivity {
ArFragment arFragment;
boolean shouldAddModel = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
arFragment = (CustomArFragment) getSupportFragmentManager().findFragmentById(R.id.sceneform_fragment);
arFragment.getPlaneDiscoveryController().hide();
arFragment.getArSceneView().getScene().addOnUpdateListener(this::onUpdateFrame);
}
@RequiresApi(api = Build.VERSION_CODES.N)
private void placeObject(ArFragment arFragment, Anchor anchor, Uri uri) {
ModelRenderable.builder()
.setSource(arFragment.getContext(), uri)
.build()
.thenAccept(modelRenderable -> addNodeToScene(arFragment, anchor, modelRenderable))
.exceptionally(throwable -> {
Toast.makeText(arFragment.getContext(), "Error:" + throwable.getMessage(), Toast.LENGTH_LONG).show();
return null;
}
);
}
@RequiresApi(api = Build.VERSION_CODES.N)
private void onUpdateFrame(FrameTime frameTime) {
Frame frame = arFragment.getArSceneView().getArFrame();
Collection<AugmentedImage> augmentedImages = frame.getUpdatedTrackables(AugmentedImage.class);
for (AugmentedImage augmentedImage : augmentedImages) {
if (augmentedImage.getTrackingState() == TrackingState.TRACKING) {
if (augmentedImage.getName().equals("tiger") && shouldAddModel) {
placeObject(arFragment, augmentedImage.createAnchor(augmentedImage.getCenterPose()), Uri.parse("Mesh_BengalTiger.sfb"));
shouldAddModel = false;
}
}
}
}
public boolean setupAugmentedImagesDb(Config config, Session session) {
AugmentedImageDatabase augmentedImageDatabase;
Bitmap bitmap = loadAugmentedImage();
if (bitmap == null) {
return false;
}
augmentedImageDatabase = new AugmentedImageDatabase(session);
augmentedImageDatabase.addImage("tiger", bitmap);
config.setAugmentedImageDatabase(augmentedImageDatabase);
return true;
}
private Bitmap loadAugmentedImage() {
try (InputStream is = getAssets().open("blanket.jpeg")) {
return BitmapFactory.decodeStream(is);
} catch (IOException e) {
Log.e("ImageLoad", "IO Exception", e);
}
return null;
}
private void addNodeToScene(ArFragment arFragment, Anchor anchor, Renderable renderable) {
AnchorNode anchorNode = new AnchorNode(anchor);
TransformableNode node = new TransformableNode(arFragment.getTransformationSystem());
node.setRenderable(renderable);
node.setParent(anchorNode);
arFragment.getArSceneView().getScene().addChild(anchorNode);
node.select();
}
}
现在运行你的应用程序 您应该看到如下所示的屏幕。在我们的手机上移动一点参考物体。ARCore将检测特征点,一旦在现实世界中检测到参考图像,它就会将3D模型添加到其中。
有了这个,我们使用Arcore by Google和Sceneform SDK创建了我们的第一个增强图像应用程序!
效果图