แก้ภาพสั่นไหวในกล้องบน Android SDK (Kotlin/Java)

ตอนนี้ ARCore รองรับระบบป้องกันภาพสั่นไหวแบบอิเล็กทรอนิกส์ (EIS) ซึ่งจะช่วยให้การแสดงตัวอย่างจากกล้องเป็นไปอย่างราบรื่น EIS รักษาภาพสั่นไหวด้วยการสังเกตการเคลื่อนไหวของโทรศัพท์โดยใช้เครื่องวัดการหมุนและใช้ Mesh ของการชดเชยค่าชดเชยภายในขอบเขตของพื้นผิวกล้องซึ่งจะช่วยรับมือกับการสั่นสะเทือนของผู้เยาว์ EIS รองรับเฉพาะในแนวตั้งของอุปกรณ์ ARCore รุ่น 1.39.0 จะรองรับการวางแนวทั้งหมด

การค้นหาการรองรับ 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 เป็นเอาต์พุตเพื่อรับพิกัดของอุปกรณ์ 3 มิติและ EIS_TEXTURE_NORMALIZED เป็นเอาต์พุตเพื่อรับพิกัดพื้นผิว 3 มิติ ปัจจุบันพิกัดประเภทอินพุตเดียวที่สนับสนุนสำหรับ 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 ปิดอยู่ พิกัด 3 มิติของเอาต์พุตจะเทียบเท่ากับค่าคู่ 2 มิติ โดยตั้งค่า z ไว้ไม่ให้มีการเปลี่ยนแปลง

แก้ไขตัวปรับแสงเงา

พิกัด 3 มิติที่คำนวณได้ควรส่งผ่านไปยังตัวปรับแสงเงาพื้นหลัง ตอนนี้บัฟเฟอร์ Vertex เป็นแบบ 3 มิติด้วย 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