ייצוב תמונות מצלמה ב-Android SDK (Kotlin/Java)

ARCore תומך עכשיו בייצוב תמונה אלקטרוני (EIS), שעוזר ליצור תצוגה מקדימה חלקה של המצלמה. טכנולוגיית EIS משיגה ייצוב על ידי צפייה בתנועת הטלפון באמצעות ג'יירו והחלה של רשת הומוגרפיה פיצוי בתוך גבולות מרקם המצלמה שמודדים את הרעידות הקלות. EIS נתמך רק בפריסה לאורך של המכשיר. כל הכיוונים ייתמכו בגרסה 1.39.0 של ARCore.

שאילתה לתמיכה ב-EIS ולהפעיל את EIS

כדי להפעיל EIS, צריך להגדיר את הסשן לשימוש ב-ImageStabilizationMode.EIS. אם המכשיר לא תומך בתכונת EIS, המערכת תתעלם מ-ARCore.

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 }
)

שינוי קואורדינטות

כש-EIS מופעל, כלי הרינדור צריך להשתמש בקואורדינטות של המכשיר ששונו ובקואורדינטות הטקסטורה התואמות, שכוללות את פיצוי ה-EIS בעיבוד רקע המצלמה. כדי לקבל את הקואורדינטות המתגמלות של EIS, משתמשים ב-Frame.transformCoordinates3d() ובשימוש ב-OPENGL_NORMALIZED_DEVICE_COORDINATES כקלט וב-EIS_NORMALIZED_DEVICE_COORDINATES כפלט לקבלת קואורדינטות מכשיר בתלת-ממד וב-EIS_TEXTURE_NORMALIZED כפלט לקבלת קואורדינטות של טקסטורה תלת-ממדית. נכון לעכשיו, הסוג היחיד של קואורדינטות הקלט הנתמך עבור Frame.transformCoordinates3d() הוא OPENGL_NORMALIZED_DEVICE_COORDINATES.

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)

כאשר EIS כבוי, הקואורדינטות התלת-ממדיות של הפלט שוות ערך לקואורדינטות הדו-ממדיות שלהן, כאשר ערכי z מוגדרים כך שלא יניבו שינוי.

שינוי של תוכנות הצללה

יש להעביר את הקואורדינטות התלת-ממדיות שמחושבות עליהן אל תוכנות הצללה לעיבוד רקע. חוצץ הקודקודים הוא עכשיו תלת-ממדי עם 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;
}

בנוסף, בכלי ההצללה של המקטעים צריך להחיל תיקון של נקודת המבט:

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);
}

לפרטים נוספים, אפשר לעיין באפליקציה לדוגמה hello_eis_kotlin.