Android SDK에서 카메라 이미지 손떨림 보정 (Kotlin/자바)

ARCore는 이제 매끄러운 카메라 미리보기를 생성하는 데 도움이 되는 전자식 이미지 손떨림 보정 (EIS)을 지원합니다. EIS는 자이로를 사용하여 휴대전화의 움직임을 관찰하고 미세한 흔들림을 보정하는 카메라 텍스처의 경계 내에 보상 호모그래피 메시를 적용하여 안정화합니다. EIS는 기기의 세로 모드 방향에서만 지원됩니다. ARCore 1.39.0 버전에서 모든 방향이 지원됩니다.

EIS 지원 쿼리 및 EIS 사용 설정

EIS를 사용 설정하려면 ImageStabilizationMode.EIS를 사용하도록 세션을 구성하세요. 기기에서 EIS 기능을 지원하지 않으면 ARCore에서 예외가 발생합니다.

자바

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를 출력으로 사용하여 3D 기기 좌표를 가져오고 EIS_TEXTURE_NORMALIZED을 출력으로 사용하여 3D 텍스처 좌표를 가져옵니다. 현재 Frame.transformCoordinates3d()에 지원되는 유일한 입력 좌표 유형은 OPENGL_NORMALIZED_DEVICE_COORDINATES입니다.

자바

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가 사용 중지되어 있으면 출력 3D 좌표는 2D 좌표와 동일하며, z 값이 변경되지 않도록 설정됩니다.

셰이더 수정

계산된 3D 좌표는 백그라운드 렌더링 셰이더에 전달되어야 합니다. 꼭짓점 버퍼는 이제 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;
}

또한 프래그먼트 셰이더는 원근 수정을 적용해야 합니다.

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 샘플 앱을 참고하세요.