Kamerabilder im Android SDK (Kotlin/Java) stabilisieren

ARCore unterstützt jetzt die elektronische Bildstabilisierung (EIS), mit der eine flüssige Vorschau der Kamera möglich ist. EIS erzielt eine Stabilisierung, indem die Smartphonebewegung mithilfe des Gyroskops beobachtet und ein Kompenshomographie-Mesh innerhalb der Grenzen der Kameratextur angewendet wird, das die geringfügigen Erschütterungen ausgleicht. EIS wird nur im Hochformat des Geräts unterstützt. Ab Version 1.39.0 von ARCore werden alle Ausrichtungen unterstützt.

EIS-Unterstützung abfragen und EIS aktivieren

Konfigurieren Sie Ihre Sitzung für die Verwendung von ImageStabilizationMode.EIS, um EIS zu aktivieren. Wenn das Gerät die EIS-Funktion nicht unterstützt, wird eine Ausnahme von ARCore 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 umwandeln

Wenn EIS aktiviert ist, muss der Renderer die geänderten Geräte- und Texturkoordinaten verwenden, die die EIS-Kompensation beim Rendern des Kamerahintergrunds enthalten. Verwenden Sie zum Abrufen der EIS-kompensierten Koordinaten Frame.transformCoordinates3d() mit 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 wird für Frame.transformCoordinates3d() nur der Eingabekoordinatentyp OPENGL_NORMALIZED_DEVICE_COORDINATES unterstützt.

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 EIS deaktiviert ist, entsprechen die 3D-Ausgabekoordinaten ihren 2D-Gegenstücken, wobei die z-Werte so eingestellt sind, dass sich keine Änderung ergibt.

Shader ändern

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

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 Perspektivenkorrektur 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.