Ổn định hình ảnh máy ảnh trên SDK Android (Kotlin/Java)

ARCore hiện hỗ trợ tính năng Chống rung hình ảnh điện tử (EIS), giúp tạo ra hình ảnh xem trước mượt mà cho máy ảnh. EIS đạt được độ ổn định bằng cách quan sát chuyển động của điện thoại bằng con quay hồi chuyển và áp dụng lưới tương đồng bù trong ranh giới của hoạ tiết máy ảnh để chống lại các rung lắc nhỏ. EIS chỉ được hỗ trợ theo hướng dọc của thiết bị. Tất cả các hướng sẽ được hỗ trợ trong bản phát hành ARCore 1.39.0.

Truy vấn tính năng hỗ trợ EIS và bật EIS

Để bật EIS, hãy định cấu hình phiên của bạn để sử dụng ImageStabilizationMode.EIS. Nếu thiết bị không hỗ trợ tính năng EIS, thì hệ thống sẽ gửi một trường hợp ngoại lệ từ 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 }
)

Chuyển đổi toạ độ

Khi bật EIS, trình kết xuất cần sử dụng toạ độ thiết bị đã sửa đổi và toạ độ kết cấu khớp với cơ chế bù EIS khi kết xuất nền của máy ảnh. Để nhận toạ độ được bù EIS, hãy sử dụng Frame.transformCoordinates3d(), sử dụng OPENGL_NORMALIZED_DEVICE_COORDINATES làm dữ liệu đầu vào và EIS_NORMALIZED_DEVICE_COORDINATES làm dữ liệu đầu ra để nhận toạ độ thiết bị 3D và EIS_TEXTURE_NORMALIZED làm dữ liệu đầu ra để nhận toạ độ hoạ tiết 3D. Hiện tại, kiểu toạ độ đầu vào duy nhất được hỗ trợ cho 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)

Khi EIS tắt, toạ độ 3D đầu ra sẽ tương đương với các toạ độ 2D, với các giá trị z được đặt để không tạo ra thay đổi.

Sửa đổi chương trình đổ bóng

Các toạ độ 3D đã tính toán cần được chuyển đến chương trình đổ bóng kết xuất nền. Vùng đệm đỉnh hiện là 3D với 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;
}

Ngoài ra, chương trình đổ bóng mảnh cần áp dụng tính năng chỉnh sửa phối cảnh:

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

Hãy xem ứng dụng mẫu hello_eis_kotlin để biết thêm thông tin chi tiết.