Combine image with video stream on Android
我正在研究Android上的增强现实。
我正在Android应用程序中使用ARCore和Sceneform。
我已经尝试了示例项目,现在想开发自己的应用程序。
我想实现的一种效果是将图像(例如.jpeg或.png)与来自摄像机内置设备的实时供稿合并/叠加。
图像将具有透明背景,允许用户同时查看实时供稿和图像
但是我不希望叠加图像成为固定/静态水印,当用户放大,缩小或平移时,叠加图像也必须放大,缩小和平移等。
我不希望过度播放的图像成为3d或任何具有这种性质的图像。
Sceneform可以实现这种效果吗?还是我需要使用其他第三方库和/或工具才能获得所需的结果。
更新
用户正在一张白纸上绘图。调整纸张的方向,使用户可以舒适地绘画(左手或右手)。用户在完成图像处理时可以自由移动纸张。
Android设备位于纸片上方,可以拍摄用户绘制所选图像的画面。
实时摄像机的提要正在投射到大电视或监视器屏幕上。
为帮助用户,他们已将静态图像选择为" trace "或" Copy "。
此图像是在Android设备上选择的,并与Android应用程序中的实时摄像头流合并。
用户可以放大和缩小其图形,并且组合的实时流和选定的静态图像也将进行放大和缩小,这将使用户可以通过绘制" Free Hand"来精确复制选定的静态图像。 "。
当用户直接看纸时,他们只能看到自己的图纸。
当用户在电视或监视器上观看他们的绘画的实况直播流时,他们会看到他们的绘画和所选的静态图像叠加在一起。用户可以控制静态图像的透明度,以帮助他们精确复制静态图像。
我认为您正在寻找的是使用AR来显示图像,以使图像保持原位,例如在一张纸上,以便充当在纸上绘制图像副本的指导。
有2个部分。首先是找到纸,其次是将图像放在纸上,并在手机四处移动时将其保持在该位置。
仅通过检测纸张的平面即可找到纸张(具有一定的对比度,图案或其他东西,而与普通的白色纸张比较有帮助),然后轻按页面中心的位置是。这是在HelloSceneform示例中完成的。
如果要使纸张装订得更准确,可以点击纸张的四个角,然后在其中创建锚点。为此,在
中注册一个平面监听的侦听器
1 | arFragment.setOnTapArPlaneListener(this::onPlaneTapped); |
然后在onPlaneTapped中,创建4个anchorNode。一旦有了4,就初始化要显示的图形。
1 2 3 4 5 6 7 8 9 10 11 | 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 Texture。这可以来自资源或文件URL。您希望纹理显示整个图像,并在调整包含它的模型的大小时进行缩放。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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布置四边形的逻辑相同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | 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节点,以便可以将图像微移到适当的位置,旋转或缩放为最佳大小。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | 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); } |
可以使用ARobjects缩放所需的AR参考图像,以此为用户调整模板大小。
更复杂的AR图像将不容易工作,因为AR图像会覆盖在用户跟踪的顶部,这会阻塞他们的笔/铅笔的笔尖。
我的解决方案是将白皮书抠像。这会将白皮书替换为所选的图像或实时供稿。除非您有跟踪纸张位置的方法,否则按照您指定的方向移动纸张将是一个问题。
如本例所示,AR对象在前面,而抠像是背景。跟踪表面(纸张)将在中心。
此示例的引用在下面的链接上。
RJ
- YouTube-AR追踪环境