Usar o ARCore como entrada para modelos de machine learning

Você pode 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 Kit de ML do ARCore demonstra como usar o Kit de ML 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.

O exemplo do Kit de ML do ARCore é escrito em Kotlin. Ele também está disponível como o app de exemplo ml_kotlin no repositório GitHub do SDK do ARCore.

Usar a imagem da CPU do ARCore

Por padrão, o ARCore captura pelo menos dois conjuntos de streams de imagem:

  • Um fluxo de imagens da CPU usado para reconhecimento de atributos 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 stream de imagem extra de resolução mais alta, se necessário.
  • Um fluxo de textura de GPU, que contém textura de alta resolução, geralmente com resolução de 1080p. Normalmente, isso é usado como uma visualização da câmera voltada para o usuário. Isso é armazenado na textura do OpenGL especificada por Session.setCameraTextureName().
  • Qualquer transmissão adicional especificada por SharedCamera.setAppSurfaces().

Considerações sobre o tamanho da imagem da CPU

Não haverá custo adicional se o stream de CPU padrão de tamanho VGA for usado, já que o ARCore usa esse stream para compreensão mundial. Solicitar um stream com uma resolução diferente pode ser caro, porque será necessário capturar outro stream. Lembre-se de que uma resolução mais alta pode custar rapidamente para seu modelo: dobrar a largura e a altura da imagem quadruplica a quantidade de pixels na imagem.

Pode ser vantajoso reduzir o tamanho da imagem, caso o modelo ainda tenha um bom desempenho em uma imagem de resolução mais baixa.

Configurar um fluxo extra de imagem da CPU de alta resolução

O desempenho do seu modelo de ML depende da resolução da imagem usada como entrada. A resolução desses streams pode ser ajustada mudando o CameraConfig atual usando Session.setCameraConfig(), selecionando uma configuração válida de 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)

Extrair a imagem da CPU

Extraia a imagem da CPU usando Frame.acquireCameraImage(). Descarte essas imagens assim que não forem mais 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

Para processar a imagem da CPU, várias bibliotecas de machine learning podem ser usadas.

Mostrar resultados na sua cena de RA

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

Usando o ponto ou centro da caixa delimitadora que é gerada pelo 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 coordenadas 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 estas coordenadas VIEW para realizar um teste de hit e criar uma âncora com base no 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 a capacidade de processamento e consumir menos energia:

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

Próximas etapas