在 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