Usar o ARCore como entrada para modelos de machine learning

É possível usar o feed da câmera capturado pelo ARCore em um pipeline de machine learning para criar uma experiência inteligente de realidade aumentada. O exemplo do ARCore ML Kit demonstra como usar o ML Kit e a API Google Cloud Vision para identificar objetos reais. O exemplo usa um modelo de machine learning para classificar objetos na visualização da câmera e anexa um rótulo a eles na cena virtual.

A amostra do kit de ML do ARCore é em Kotlin. Ele também está disponível como exemplo de ml_kotlin. no SDK do ARCore repositório do GitHub.

Usar a imagem de CPU do ARCore

O ARCore captura pelo menos dois conjuntos de fluxos de imagem por padrão:

  • Um stream de imagem da CPU usado para reconhecimento de recursos e processamento de imagens. Por padrão, a imagem da CPU tem uma resolução VGA (640 x 480). O ARCore pode ser configurado para usar um fluxo de imagem de resolução mais alta, se necessário.
  • Um stream de textura da GPU, que contém uma textura de alta resolução, geralmente com resolução de 1080p. Isso normalmente é usado como uma visualização da câmera voltada para o usuário. Ela é armazenada na textura OpenGL especificada por Session.setCameraTextureName().
  • Todos os streams adicionais especificados por SharedCamera.setAppSurfaces().

Considerações sobre o tamanho da imagem da CPU

Nenhum custo extra vai ser gerado se o stream de CPU padrão com tamanho de VGA for usado, porque o ARCore usa esse stream para compreensão do mundo. Solicitar um stream com uma resolução diferente pode ser caro, porque será necessário capturar um stream adicional. Lembre-se de que uma resolução mais alta pode rapidamente se tornar cara para seu modelo: dobrar a largura e a altura da imagem quadruplica a quantidade de pixels nela.

Pode ser vantajoso reduzir a imagem se o modelo ainda puder ter um bom desempenho em uma imagem de resolução mais baixa.

Configurar outro stream de imagem da CPU de alta resolução

O desempenho do seu modelo de ML pode depender da resolução da imagem usada como entrada. Para ajustar a resolução desses streams, mude o CameraConfig atual usando Session.setCameraConfig() e selecione uma configuração válida em Session.getSupportedCameraConfigs().

Java

CameraConfigFilter cameraConfigFilter =
    new CameraConfigFilter(session)
        // World-facing cameras only.
        .setFacingDirection(CameraConfig.FacingDirection.BACK);
List<CameraConfig> supportedCameraConfigs =
    session.getSupportedCameraConfigs(cameraConfigFilter);

// Select an acceptable configuration from supportedCameraConfigs.
CameraConfig cameraConfig = selectCameraConfig(supportedCameraConfigs);
session.setCameraConfig(cameraConfig);

Kotlin

val cameraConfigFilter =
  CameraConfigFilter(session)
    // World-facing cameras only.
    .setFacingDirection(CameraConfig.FacingDirection.BACK)
val supportedCameraConfigs = session.getSupportedCameraConfigs(cameraConfigFilter)

// Select an acceptable configuration from supportedCameraConfigs.
val cameraConfig = selectCameraConfig(supportedCameraConfigs)
session.setCameraConfig(cameraConfig)

Recuperar a imagem da CPU

Recupere a imagem da CPU usando Frame.acquireCameraImage(). Descarte-as assim que deixar de ser necessárias.

Java

Image cameraImage = null;
try {
  cameraImage = frame.acquireCameraImage();
  // Process `cameraImage` using your ML inference model.
} catch (NotYetAvailableException e) {
  // NotYetAvailableException is an exception that can be expected when the camera is not ready
  // yet. The image may become available on a next frame.
} catch (RuntimeException e) {
  // A different exception occurred, e.g. DeadlineExceededException, ResourceExhaustedException.
  // Handle this error appropriately.
  handleAcquireCameraImageFailure(e);
} finally {
  if (cameraImage != null) {
    cameraImage.close();
  }
}

Kotlin

// NotYetAvailableException is an exception that can be expected when the camera is not ready yet.
// Map it to `null` instead, but continue to propagate other errors.
fun Frame.tryAcquireCameraImage() =
  try {
    acquireCameraImage()
  } catch (e: NotYetAvailableException) {
    null
  } catch (e: RuntimeException) {
    // A different exception occurred, e.g. DeadlineExceededException, ResourceExhaustedException.
    // Handle this error appropriately.
    handleAcquireCameraImageFailure(e)
  }

// The `use` block ensures the camera image is disposed of after use.
frame.tryAcquireCameraImage()?.use { image ->
  // Process `image` using your ML inference model.
}

Processar a imagem da CPU

Várias bibliotecas de machine learning podem ser usadas para processar a imagem da CPU.

Mostrar resultados na sua cena de RA

Os modelos de reconhecimento de imagem muitas vezes emitem os objetos detectados indicando um ponto central ou um polígono delimitador que representa o objeto detectado.

Usando o ponto central ou o centro da caixa delimitadora da saída do modelo, é possível anexar uma âncora ao objeto detectado. Use Frame.hitTest() para estimar a pose de um objeto na cena virtual.

Converta coordenadas IMAGE_PIXELS em VIEW:

Java

// Suppose `mlResult` contains an (x, y) of a given point on the CPU image.
float[] cpuCoordinates = new float[] {mlResult.getX(), mlResult.getY()};
float[] viewCoordinates = new float[2];
frame.transformCoordinates2d(
    Coordinates2d.IMAGE_PIXELS, cpuCoordinates, Coordinates2d.VIEW, viewCoordinates);
// `viewCoordinates` now contains coordinates suitable for hit testing.

Kotlin

// Suppose `mlResult` contains an (x, y) of a given point on the CPU image.
val cpuCoordinates = floatArrayOf(mlResult.x, mlResult.y)
val viewCoordinates = FloatArray(2)
frame.transformCoordinates2d(
  Coordinates2d.IMAGE_PIXELS,
  cpuCoordinates,
  Coordinates2d.VIEW,
  viewCoordinates
)
// `viewCoordinates` now contains coordinates suitable for hit testing.

Use essas coordenadas VIEW para realizar um teste de hit e criar uma âncora a partir do resultado:

Java

List<HitResult> hits = frame.hitTest(viewCoordinates[0], viewCoordinates[1]);
HitResult depthPointResult = null;
for (HitResult hit : hits) {
  if (hit.getTrackable() instanceof DepthPoint) {
    depthPointResult = hit;
    break;
  }
}
if (depthPointResult != null) {
  Anchor anchor = depthPointResult.getTrackable().createAnchor(depthPointResult.getHitPose());
  // This anchor will be attached to the scene with stable tracking.
  // It can be used as a position for a virtual object, with a rotation prependicular to the
  // estimated surface normal.
}

Kotlin

val hits = frame.hitTest(viewCoordinates[0], viewCoordinates[1])
val depthPointResult = hits.filter { it.trackable is DepthPoint }.firstOrNull()
if (depthPointResult != null) {
  val anchor = depthPointResult.trackable.createAnchor(depthPointResult.hitPose)
  // This anchor will be attached to the scene with stable tracking.
  // It can be used as a position for a virtual object, with a rotation prependicular to the
  // estimated surface normal.
}

Considerações sobre desempenho

Siga as recomendações a seguir para economizar energia de processamento e consumir menos energia:

  • Não execute seu modelo de ML em cada frame recebido. Em vez disso, considere executar a detecção de objetos com um frame rate baixo.
  • Considere usar um modelo de inferência de ML on-line para reduzir a complexidade computacional.

Próximas etapas