Kamerabilder im Android SDK (Kotlin/Java) stabilisieren

ARCore unterstützt jetzt die elektronische Bildstabilisierung (EIS), wodurch eine flüssige Kameravorschau möglich ist. Die Stabilisierung durch EIS wird durch Beobachtung der Smartphone-Bewegung mit dem Gyroskop und durch Anwenden eines Homöomorphie-Netzes zur Kompensation innerhalb der Grenzen der Kameratextur erreicht, um kleinere Verwacklungen zu korrigieren. Die Funktion wird nur im Hochformat unterstützt. In der Version 1.39.0 von ARCore werden alle Ausrichtungen unterstützt.

EIS-Support anfordern und EIS aktivieren

Wenn Sie EIS aktivieren möchten, konfigurieren Sie Ihre Sitzung für die Verwendung von ImageStabilizationMode.EIS. Wenn das Gerät die EIS-Funktion nicht unterstützt, wird von ARCore eine Ausnahme ausgelöst.

Java

if (!session.isImageStabilizationModeSupported(Config.ImageStabilizationMode.EIS)) {
  return;
}
Config config = session.getConfig();
config.setImageStabilizationMode(Config.ImageStabilizationMode.EIS);
session.configure(config);

Kotlin

if (!session.isImageStabilizationModeSupported(Config.ImageStabilizationMode.EIS)) return
session.configure(
  session.config.apply { imageStabilizationMode = Config.ImageStabilizationMode.EIS }
)

Koordinaten transformieren

Wenn die Funktion aktiviert ist, muss der Renderer die geänderten Gerätekoordinaten und die zugehörigen Texturkoordinaten verwenden, die die EIS-Kompensation beim Rendern des Kamerahintergrunds berücksichtigen. Verwenden Sie Frame.transformCoordinates3d(), um die mit EIS kompensierten Koordinaten zu erhalten. Verwenden Sie OPENGL_NORMALIZED_DEVICE_COORDINATES als Eingabe und EIS_NORMALIZED_DEVICE_COORDINATES als Ausgabe, um 3D-Gerätekoordinaten zu erhalten, und EIS_TEXTURE_NORMALIZED als Ausgabe, um 3D-Texturkoordinaten zu erhalten. Derzeit ist OPENGL_NORMALIZED_DEVICE_COORDINATES der einzige unterstützte Koordinatentyp für Frame.transformCoordinates3d().

Java

final FloatBuffer cameraTexCoords =
    ByteBuffer.allocateDirect(COORDS_BUFFER_SIZE_3D)
        .order(ByteOrder.nativeOrder())
        .asFloatBuffer();

final FloatBuffer screenCoords =
    ByteBuffer.allocateDirect(COORDS_BUFFER_SIZE_3D)
        .order(ByteOrder.nativeOrder())
        .asFloatBuffer();

final FloatBuffer NDC_QUAD_COORDS_BUFFER =
    ByteBuffer.allocateDirect(COORDS_BUFFER_SIZE_2D)
        .order(ByteOrder.nativeOrder())
        .asFloatBuffer()
        .put(
            new float[] {
              /*0:*/ -1f, -1f, /*1:*/ +1f, -1f, /*2:*/ -1f, +1f, /*3:*/ +1f, +1f,
            });

final VertexBuffer screenCoordsVertexBuffer =
    new VertexBuffer(render, /* numberOfEntriesPerVertex= */ 3, null);
final VertexBuffer cameraTexCoordsVertexBuffer =
    new VertexBuffer(render, /* numberOfEntriesPerVertex= */ 3, null);

NDC_QUAD_COORDS_BUFFER.rewind();
frame.transformCoordinates3d(
    Coordinates2d.OPENGL_NORMALIZED_DEVICE_COORDINATES,
    NDC_QUAD_COORDS_BUFFER,
    Coordinates3d.EIS_NORMALIZED_DEVICE_COORDINATES,
    screenCoords);
screenCoordsVertexBuffer.set(screenCoords);

NDC_QUAD_COORDS_BUFFER.rewind();
frame.transformCoordinates3d(
    Coordinates2d.OPENGL_NORMALIZED_DEVICE_COORDINATES,
    NDC_QUAD_COORDS_BUFFER,
    Coordinates3d.EIS_TEXTURE_NORMALIZED,
    cameraTexCoords);
cameraTexCoordsVertexBuffer.set(cameraTexCoords);

Kotlin

val COORDS_BUFFER_SIZE_2D = 2 * 4 * Float.SIZE_BYTES
val COORDS_BUFFER_SIZE_3D = 3 * 4 * Float.SIZE_BYTES
val cameraTexCoords =
  ByteBuffer.allocateDirect(COORDS_BUFFER_SIZE_3D)
    .order(ByteOrder.nativeOrder())
    .asFloatBuffer()
val screenCoords =
  ByteBuffer.allocateDirect(COORDS_BUFFER_SIZE_3D)
    .order(ByteOrder.nativeOrder())
    .asFloatBuffer()
val cameraTexCoordsVertexBuffer = VertexBuffer(render, /* numberOfEntriesPerVertex= */ 3, null)
val screenCoordsVertexBuffer = VertexBuffer(render, /* numberOfEntriesPerVertex= */ 3, null)
val NDC_QUAD_COORDS_BUFFER =
  ByteBuffer.allocateDirect(COORDS_BUFFER_SIZE_2D)
    .order(ByteOrder.nativeOrder())
    .asFloatBuffer()
    .apply {
      put(
        floatArrayOf(
          /* 0: */
          -1f,
          -1f,
          /* 1: */
          +1f,
          -1f,
          /* 2: */
          -1f,
          +1f,
          /* 3: */
          +1f,
          +1f
        )
      )
    }
NDC_QUAD_COORDS_BUFFER.rewind()
frame.transformCoordinates3d(
  Coordinates2d.OPENGL_NORMALIZED_DEVICE_COORDINATES,
  NDC_QUAD_COORDS_BUFFER,
  Coordinates3d.EIS_NORMALIZED_DEVICE_COORDINATES,
  screenCoords
)
screenCoordsVertexBuffer.set(screenCoords)

NDC_QUAD_COORDS_BUFFER.rewind()
frame.transformCoordinates3d(
  Coordinates2d.OPENGL_NORMALIZED_DEVICE_COORDINATES,
  NDC_QUAD_COORDS_BUFFER,
  Coordinates3d.EIS_TEXTURE_NORMALIZED,
  cameraTexCoords
)
cameraTexCoordsVertexBuffer.set(cameraTexCoords)

Wenn die Funktion deaktiviert ist, entsprechen die Ausgabe-3D-Koordinaten den 2D-Koordinaten. Die Z-Werte sind so festgelegt, dass keine Änderungen vorgenommen werden.

Shader ändern

Die berechneten 3D-Koordinaten sollten an Shader für das Hintergrund-Rendering übergeben werden. Die Vertex-Buffer sind jetzt mit EIS 3D:

layout(location = 0) in vec4 a_Position;
layout(location = 1) in vec3 a_CameraTexCoord;
out vec3 v_CameraTexCoord;
void main() {
  gl_Position = a_Position;
  v_CameraTexCoord = a_CameraTexCoord;
}

Außerdem muss der Fragment-Shader eine perspektivische Korrektur anwenden:

precision mediump float;
uniform samplerExternalOES u_CameraColorTexture;
in vec3 v_CameraTexCoord;
layout(location = 0) out vec4 o_FragColor;
void main() {
  vec3 tc = (v_CameraTexCoord / v_CameraTexCoord.z);
  o_FragColor = texture(u_CameraColorTexture, tc.xy);
}

Weitere Informationen finden Sie in der Beispiel-App hello_eis_kotlin.