在 Android 上将图像与视频流合并
Combine image with video stream on Android
我正在研究 Android 上的增强现实。
我在 Android 应用程序中使用 ARCore 和 Sceneform。
我已经试用了示例项目,现在想开发自己的应用程序。
我想要实现的一个效果是 combine/overlay 一张图像(例如 .jpeg 或 .png),其中包含来自设备机载摄像头的实时信息。
图像将具有透明背景,允许用户同时查看实时提要和图像
但是我不希望叠加的图像是fixed/static水印,当用户放大、缩小或平移叠加图像时,也必须放大、缩小和平移等
我不希望过度播放的图像变成 3d 或任何类似性质的图像。
Sceneform 可以实现这种效果吗?或者我需要使用其他第 3 方库 and/or 工具来达到预期的结果。
更新
用户正在空白的 sheet 白纸上作画。纸张 sheet 的方向使用户可以舒适地绘画(无论是左手还是右手)。用户在完成图像时可以自由移动 sheet 纸张。
一个 Android 设备被放在 sheet 的纸上,拍摄用户绘制他们选择的图像。
正在将实时摄像头画面投射到大电视或监视器屏幕上。
为了帮助用户,他们选择了一张静态图像 "trace" 或 "Copy"。
此图像是在 Android 设备上选择的,并且正在 Android 应用程序中与实时摄像头流结合。
用户可以放大和缩小他们的绘图,组合的实时流和选定的静态图像也会放大和缩小,这将使用户能够通过绘图精确复制选定的静态图像"Free Hand".
当用户直接看 sheet 纸时,他们只会看到自己的绘图。
当用户在电视或监视器上观看他们绘画的直播流时,他们会看到他们的绘画和所选的静态图像叠加在一起。用户可以控制静态图像的透明度,以帮助他们制作准确的副本。
我认为您正在寻找的是使用 AR 来显示图像,以便图像保持原位,例如在 sheet 纸上以作为绘制副本的指南纸上的图像。
这有两个部分。首先是找到纸张的 sheet,其次是将图像放在纸张上并在 phone 移动时将其保持在那里。
定位纸张 sheet 只需检测纸张所在的平面即可(对比纯白色 sheet 纸张会有一些对比、图案或其他东西会有所帮助),然后点击页面中心的位置。这是在 HelloSceneform 示例中完成的。
如果你想让纸张的边界更准确,你可以点击纸张的四个角,然后在那里创建锚点。为此,请在 onCreate()
中注册一个平面监听器
arFragment.setOnTapArPlaneListener(this::onPlaneTapped);
然后在onPlaneTapped中,创建4个anchorNodes。一旦你有4个,初始化要显示的绘图。
private void onPlaneTapped(HitResult hitResult, Plane plane, MotionEvent event) {
if (cornerAnchors.size() != 4) {
AnchorNode corner = createCornerNode(hitResult.createAnchor());
arFragment.getArSceneView().getScene().addChild(corner);
cornerAnchors.add(corner);
}
if (cornerAnchors.size() == 4 && drawingNode == null) {
initializeDrawing();
}
}
要初始化绘图,请从位图或可绘制对象创建一个 Sceneform 纹理。这可以来自资源或文件 URL。您希望纹理显示整个图像,并随着持有它的模型的大小调整而缩放。
private void initializeDrawing() {
Texture.Sampler sampler = Texture.Sampler.builder()
.setWrapMode(Texture.Sampler.WrapMode.CLAMP_TO_EDGE)
.setMagFilter(Texture.Sampler.MagFilter.NEAREST)
.setMinFilter(Texture.Sampler.MinFilter.LINEAR_MIPMAP_LINEAR)
.build();
Texture.builder()
.setSource(this, R.drawable.logo_google_developers)
.setSampler(sampler)
.build()
.thenAccept(texture -> {
MaterialFactory.makeTransparentWithTexture(this, texture)
.thenAccept(this::buildDrawingRenderable);
});
}
保存纹理的模型只是一个平面四边形,大小为角之间的最小尺寸。这与使用 OpenGL 布置四边形的逻辑相同。
private void buildDrawingRenderable(Material material) {
Integer[] indices = {
0, 1, 3, 3, 1, 2
};
//Calculate the center of the corners.
float min_x = Float.MAX_VALUE;
float max_x = Float.MIN_VALUE;
float min_z = Float.MAX_VALUE;
float max_z = Float.MIN_VALUE;
for (AnchorNode node : cornerAnchors) {
float x = node.getWorldPosition().x;
float z = node.getWorldPosition().z;
min_x = Float.min(min_x, x);
max_x = Float.max(max_x, x);
min_z = Float.min(min_z, z);
max_z = Float.max(max_z, z);
}
float width = Math.abs(max_x - min_x);
float height = Math.abs(max_z - min_z);
float extent = Math.min(width / 2, height / 2);
Vertex[] vertices = {
Vertex.builder()
.setPosition(new Vector3(-extent, 0, extent))
.setUvCoordinate(new Vertex.UvCoordinate(0, 1)) // top left
.build(),
Vertex.builder()
.setPosition(new Vector3(extent, 0, extent))
.setUvCoordinate(new Vertex.UvCoordinate(1, 1)) // top right
.build(),
Vertex.builder()
.setPosition(new Vector3(extent, 0, -extent))
.setUvCoordinate(new Vertex.UvCoordinate(1, 0)) // bottom right
.build(),
Vertex.builder()
.setPosition(new Vector3(-extent, 0, -extent))
.setUvCoordinate(new Vertex.UvCoordinate(0, 0)) // bottom left
.build()
};
RenderableDefinition.Submesh[] submeshes = {
RenderableDefinition.Submesh.builder().
setMaterial(material)
.setTriangleIndices(Arrays.asList(indices))
.build()
};
RenderableDefinition def = RenderableDefinition.builder()
.setSubmeshes(Arrays.asList(submeshes))
.setVertices(Arrays.asList(vertices)).build();
ModelRenderable.builder().setSource(def)
.setRegistryId("drawing").build()
.thenAccept(this::positionDrawing);
}
最后一部分是将四边形定位在角的中心,并创建一个 Transformable 节点,这样图像就可以被微调到位、旋转或缩放到完美的尺寸。
private void positionDrawing(ModelRenderable drawingRenderable) {
//Calculate the center of the corners.
float min_x = Float.MAX_VALUE;
float max_x = Float.MIN_VALUE;
float min_z = Float.MAX_VALUE;
float max_z = Float.MIN_VALUE;
for (AnchorNode node : cornerAnchors) {
float x = node.getWorldPosition().x;
float z = node.getWorldPosition().z;
min_x = Float.min(min_x, x);
max_x = Float.max(max_x, x);
min_z = Float.min(min_z, z);
max_z = Float.max(max_z, z);
}
Vector3 center = new Vector3((min_x + max_x) / 2f,
cornerAnchors.get(0).getWorldPosition().y, (min_z + max_z) / 2f);
Anchor centerAnchor = null;
Vector3 screenPt = arFragment.getArSceneView().getScene().getCamera().worldToScreenPoint(center);
List<HitResult> hits = arFragment.getArSceneView().getArFrame().hitTest(screenPt.x, screenPt.y);
for (HitResult hit : hits) {
if (hit.getTrackable() instanceof Plane) {
centerAnchor = hit.createAnchor();
break;
}
}
AnchorNode centerNode = new AnchorNode(centerAnchor);
centerNode.setParent(arFragment.getArSceneView().getScene());
drawingNode = new TransformableNode(arFragment.getTransformationSystem());
drawingNode.setParent(centerNode);
drawingNode.setRenderable(drawingRenderable);
}
预期的 AR 参考图像可以使用 ARobjects 进行缩放,作为用户调整模板大小的点。
更复杂的 AR 图像将无法轻松工作,因为 AR 图像覆盖在用户追踪的顶部,这会遮挡他们的尖端 pen/pencil。
我的解决方案是对白皮书进行色键。这将用所选图像或实时提要替换白皮书。按照您指定的方式移动纸张将是一个问题,除非您有跟踪纸张位置的方法。
如您在此示例中所见,AR 对象在前面,而色度键在背景中。跟踪表面(纸)将位于中心。
下面的 link 中引用了此示例。
RJ
我正在研究 Android 上的增强现实。
我在 Android 应用程序中使用 ARCore 和 Sceneform。
我已经试用了示例项目,现在想开发自己的应用程序。
我想要实现的一个效果是 combine/overlay 一张图像(例如 .jpeg 或 .png),其中包含来自设备机载摄像头的实时信息。
图像将具有透明背景,允许用户同时查看实时提要和图像
但是我不希望叠加的图像是fixed/static水印,当用户放大、缩小或平移叠加图像时,也必须放大、缩小和平移等
我不希望过度播放的图像变成 3d 或任何类似性质的图像。
Sceneform 可以实现这种效果吗?或者我需要使用其他第 3 方库 and/or 工具来达到预期的结果。
更新
用户正在空白的 sheet 白纸上作画。纸张 sheet 的方向使用户可以舒适地绘画(无论是左手还是右手)。用户在完成图像时可以自由移动 sheet 纸张。
一个 Android 设备被放在 sheet 的纸上,拍摄用户绘制他们选择的图像。
正在将实时摄像头画面投射到大电视或监视器屏幕上。
为了帮助用户,他们选择了一张静态图像 "trace" 或 "Copy"。
此图像是在 Android 设备上选择的,并且正在 Android 应用程序中与实时摄像头流结合。
用户可以放大和缩小他们的绘图,组合的实时流和选定的静态图像也会放大和缩小,这将使用户能够通过绘图精确复制选定的静态图像"Free Hand".
当用户直接看 sheet 纸时,他们只会看到自己的绘图。
当用户在电视或监视器上观看他们绘画的直播流时,他们会看到他们的绘画和所选的静态图像叠加在一起。用户可以控制静态图像的透明度,以帮助他们制作准确的副本。
我认为您正在寻找的是使用 AR 来显示图像,以便图像保持原位,例如在 sheet 纸上以作为绘制副本的指南纸上的图像。
这有两个部分。首先是找到纸张的 sheet,其次是将图像放在纸张上并在 phone 移动时将其保持在那里。
定位纸张 sheet 只需检测纸张所在的平面即可(对比纯白色 sheet 纸张会有一些对比、图案或其他东西会有所帮助),然后点击页面中心的位置。这是在 HelloSceneform 示例中完成的。
如果你想让纸张的边界更准确,你可以点击纸张的四个角,然后在那里创建锚点。为此,请在 onCreate()
arFragment.setOnTapArPlaneListener(this::onPlaneTapped);
然后在onPlaneTapped中,创建4个anchorNodes。一旦你有4个,初始化要显示的绘图。
private void onPlaneTapped(HitResult hitResult, Plane plane, MotionEvent event) {
if (cornerAnchors.size() != 4) {
AnchorNode corner = createCornerNode(hitResult.createAnchor());
arFragment.getArSceneView().getScene().addChild(corner);
cornerAnchors.add(corner);
}
if (cornerAnchors.size() == 4 && drawingNode == null) {
initializeDrawing();
}
}
要初始化绘图,请从位图或可绘制对象创建一个 Sceneform 纹理。这可以来自资源或文件 URL。您希望纹理显示整个图像,并随着持有它的模型的大小调整而缩放。
private void initializeDrawing() {
Texture.Sampler sampler = Texture.Sampler.builder()
.setWrapMode(Texture.Sampler.WrapMode.CLAMP_TO_EDGE)
.setMagFilter(Texture.Sampler.MagFilter.NEAREST)
.setMinFilter(Texture.Sampler.MinFilter.LINEAR_MIPMAP_LINEAR)
.build();
Texture.builder()
.setSource(this, R.drawable.logo_google_developers)
.setSampler(sampler)
.build()
.thenAccept(texture -> {
MaterialFactory.makeTransparentWithTexture(this, texture)
.thenAccept(this::buildDrawingRenderable);
});
}
保存纹理的模型只是一个平面四边形,大小为角之间的最小尺寸。这与使用 OpenGL 布置四边形的逻辑相同。
private void buildDrawingRenderable(Material material) {
Integer[] indices = {
0, 1, 3, 3, 1, 2
};
//Calculate the center of the corners.
float min_x = Float.MAX_VALUE;
float max_x = Float.MIN_VALUE;
float min_z = Float.MAX_VALUE;
float max_z = Float.MIN_VALUE;
for (AnchorNode node : cornerAnchors) {
float x = node.getWorldPosition().x;
float z = node.getWorldPosition().z;
min_x = Float.min(min_x, x);
max_x = Float.max(max_x, x);
min_z = Float.min(min_z, z);
max_z = Float.max(max_z, z);
}
float width = Math.abs(max_x - min_x);
float height = Math.abs(max_z - min_z);
float extent = Math.min(width / 2, height / 2);
Vertex[] vertices = {
Vertex.builder()
.setPosition(new Vector3(-extent, 0, extent))
.setUvCoordinate(new Vertex.UvCoordinate(0, 1)) // top left
.build(),
Vertex.builder()
.setPosition(new Vector3(extent, 0, extent))
.setUvCoordinate(new Vertex.UvCoordinate(1, 1)) // top right
.build(),
Vertex.builder()
.setPosition(new Vector3(extent, 0, -extent))
.setUvCoordinate(new Vertex.UvCoordinate(1, 0)) // bottom right
.build(),
Vertex.builder()
.setPosition(new Vector3(-extent, 0, -extent))
.setUvCoordinate(new Vertex.UvCoordinate(0, 0)) // bottom left
.build()
};
RenderableDefinition.Submesh[] submeshes = {
RenderableDefinition.Submesh.builder().
setMaterial(material)
.setTriangleIndices(Arrays.asList(indices))
.build()
};
RenderableDefinition def = RenderableDefinition.builder()
.setSubmeshes(Arrays.asList(submeshes))
.setVertices(Arrays.asList(vertices)).build();
ModelRenderable.builder().setSource(def)
.setRegistryId("drawing").build()
.thenAccept(this::positionDrawing);
}
最后一部分是将四边形定位在角的中心,并创建一个 Transformable 节点,这样图像就可以被微调到位、旋转或缩放到完美的尺寸。
private void positionDrawing(ModelRenderable drawingRenderable) {
//Calculate the center of the corners.
float min_x = Float.MAX_VALUE;
float max_x = Float.MIN_VALUE;
float min_z = Float.MAX_VALUE;
float max_z = Float.MIN_VALUE;
for (AnchorNode node : cornerAnchors) {
float x = node.getWorldPosition().x;
float z = node.getWorldPosition().z;
min_x = Float.min(min_x, x);
max_x = Float.max(max_x, x);
min_z = Float.min(min_z, z);
max_z = Float.max(max_z, z);
}
Vector3 center = new Vector3((min_x + max_x) / 2f,
cornerAnchors.get(0).getWorldPosition().y, (min_z + max_z) / 2f);
Anchor centerAnchor = null;
Vector3 screenPt = arFragment.getArSceneView().getScene().getCamera().worldToScreenPoint(center);
List<HitResult> hits = arFragment.getArSceneView().getArFrame().hitTest(screenPt.x, screenPt.y);
for (HitResult hit : hits) {
if (hit.getTrackable() instanceof Plane) {
centerAnchor = hit.createAnchor();
break;
}
}
AnchorNode centerNode = new AnchorNode(centerAnchor);
centerNode.setParent(arFragment.getArSceneView().getScene());
drawingNode = new TransformableNode(arFragment.getTransformationSystem());
drawingNode.setParent(centerNode);
drawingNode.setRenderable(drawingRenderable);
}
预期的 AR 参考图像可以使用 ARobjects 进行缩放,作为用户调整模板大小的点。
更复杂的 AR 图像将无法轻松工作,因为 AR 图像覆盖在用户追踪的顶部,这会遮挡他们的尖端 pen/pencil。
我的解决方案是对白皮书进行色键。这将用所选图像或实时提要替换白皮书。按照您指定的方式移动纸张将是一个问题,除非您有跟踪纸张位置的方法。
如您在此示例中所见,AR 对象在前面,而色度键在背景中。跟踪表面(纸)将位于中心。
下面的 link 中引用了此示例。
RJ