Etiqueta imágenes con un modelo entrenado por AutoML en iOS

Después de entrenar tu propio modelo con AutoML Vision Edge, puedes usarlo en tu app para etiquetar imágenes.

Antes de comenzar

  1. Si aún no agregaste Firebase a tu app, sigue los pasos en la guía de introducción para hacerlo.
  2. Incluye las bibliotecas del ML Kit en tu Podfile:
    pod 'Firebase/MLVision', '6.25.0'
    pod 'Firebase/MLVisionAutoML', '6.25.0'
    
    Después de instalar o actualizar los Pods de tu proyecto, asegúrate de abrir el proyecto de Xcode con su .xcworkspace.
  3. En tu app, importa Firebase:

    Swift

    import Firebase

    Objective-C

    @import Firebase;

1. Carga el modelo

El ML Kit ejecuta los modelos generados con AutoML en el dispositivo. Sin embargo, puedes configurar ML Kit para cargar tu modelo, ya sea de forma remota desde Firebase, desde el almacenamiento local, o ambos.

Si alojas el modelo en Firebase, puedes actualizarlo sin lanzar una nueva versión de la app y usar Remote Config y A/B Testing para entregar de forma dinámica diferentes modelos a distintos conjuntos de usuarios.

Si eliges proporcionar el modelo únicamente mediante el alojamiento con Firebase y no empaquetarlo con tu app, puedes reducir el tamaño de descarga inicial de la app. Sin embargo, ten en cuenta que, si el modelo no se empaqueta con la aplicación, las funcionalidades relacionadas con el modelo no estarán disponibles hasta que la app lo descargue por primera vez.

Si empaquetas el modelo con la app, puedes asegurarte de que las funciones de AA de tu app estén activas cuando el modelo alojado en Firebase no esté disponible.

Configura una fuente de modelo alojada en Firebase

Para usar el modelo alojado de forma remota, crea un objeto AutoMLRemoteModel y especifica el nombre que le asignaste al modelo cuando lo publicaste:

Swift

let remoteModel = AutoMLRemoteModel(
    name: "your_remote_model"  // The name you assigned in the Firebase console.
)

Objective-C

FIRAutoMLRemoteModel *remoteModel = [[FIRAutoMLRemoteModel alloc]
    initWithName:@"your_remote_model"];  // The name you assigned in the Firebase console.

Luego, inicia la tarea de descarga del modelo y especifica las condiciones bajo las que deseas permitir la descarga. Si el modelo no está en el dispositivo o si hay una versión más reciente de este, la tarea descargará el modelo de Firebase de forma asíncrona:

Swift

let downloadConditions = ModelDownloadConditions(
  allowsCellularAccess: true,
  allowsBackgroundDownloading: true
)

let downloadProgress = ModelManager.modelManager().download(
  remoteModel,
  conditions: downloadConditions
)

Objective-C

FIRModelDownloadConditions *downloadConditions =
    [[FIRModelDownloadConditions alloc] initWithAllowsCellularAccess:YES
                                         allowsBackgroundDownloading:YES];

NSProgress *downloadProgress =
    [[FIRModelManager modelManager] downloadRemoteModel:remoteModel
                                             conditions:downloadConditions];

Muchas apps comienzan la tarea de descarga en su código de inicialización, pero puedes hacerlo en cualquier momento antes de usar el modelo.

Configura una fuente de modelo local

Sigue estos pasos para empaquetar el modelo con tu app:

  1. Extrae el modelo y sus metadatos del archivo ZIP que descargaste desde Firebase console a una carpeta:
    your_model_directory
      |____dict.txt
      |____manifest.json
      |____model.tflite
    
    Los tres archivos deben estar en la misma carpeta. Te recomendamos usar los archivos tal como los descargaste, sin modificarlos (incluidos los nombres de archivos).
  2. Copia la carpeta a tu proyecto de Xcode, con el cuidado de seleccionar Crear referencias de carpetas cuando lo hagas. El archivo y los metadatos del modelo se incluirán en el paquete de la app y estarán disponibles para el ML Kit.
  3. Crea un objeto AutoMLLocalModel y especifica la ruta de acceso del archivo de manifiesto del modelo:

    Swift

    guard let manifestPath = Bundle.main.path(
        forResource: "manifest",
        ofType: "json",
        inDirectory: "your_model_directory"
    ) else { return true }
    let localModel = AutoMLLocalModel(manifestPath: manifestPath)
    

    Objective-C

    NSString *manifestPath = [NSBundle.mainBundle pathForResource:@"manifest"
                                                           ofType:@"json"
                                                      inDirectory:@"your_model_directory"];
    FIRAutoMLLocalModel *localModel = [[FIRAutoMLLocalModel alloc] initWithManifestPath:manifestPath];
    

Crea un etiquetador de imágenes a partir de tu modelo

Después de configurar las fuentes de tu modelo, crea un objeto VisionImageLabeler a partir de una de ellas.

Si solo tienes un modelo empaquetado localmente, crea un etiquetador desde el objeto AutoMLLocalModel y configura el umbral de puntuación de confianza que deseas solicitar (consulta la sección sobre cómo evaluar tu modelo):

Swift

let options = VisionOnDeviceAutoMLImageLabelerOptions(localModel: localModel)
options.confidenceThreshold = 0  // Evaluate your model in the Firebase console
                                 // to determine an appropriate value.
let labeler = Vision.vision().onDeviceAutoMLImageLabeler(options: options)

Objective-C

FIRVisionOnDeviceAutoMLImageLabelerOptions *options =
    [[FIRVisionOnDeviceAutoMLImageLabelerOptions alloc] initWithLocalModel:localModel];
options.confidenceThreshold = 0;  // Evaluate your model in the Firebase console
                                  // to determine an appropriate value.
FIRVisionImageLabeler *labeler =
    [[FIRVision vision] onDeviceAutoMLImageLabelerWithOptions:options];

Si tienes un modelo alojado de forma remota, comprueba si se descargó antes de ejecutarlo. Puedes verificar el estado de la tarea de descarga del modelo con el método isModelDownloaded(remoteModel:) del administrador del modelo.

Aunque solo tienes que confirmar esto antes de ejecutar el etiquetador, si tienes un modelo alojado de forma remota y uno empaquetado localmente, tendría sentido realizar esta verificación cuando se crea una instancia de VisionImageLabeler: crea un etiquetador desde el modelo remoto si se descargó o, en su defecto, desde el modelo local.

Swift

var options: VisionOnDeviceAutoMLImageLabelerOptions?
if (ModelManager.modelManager().isModelDownloaded(remoteModel)) {
  options = VisionOnDeviceAutoMLImageLabelerOptions(remoteModel: remoteModel)
} else {
  options = VisionOnDeviceAutoMLImageLabelerOptions(localModel: localModel)
}
options.confidenceThreshold = 0  // Evaluate your model in the Firebase console
                                 // to determine an appropriate value.
let labeler = Vision.vision().onDeviceAutoMLImageLabeler(options: options)

Objective-C

VisionOnDeviceAutoMLImageLabelerOptions *options;
if ([[FIRModelManager modelManager] isModelDownloaded:remoteModel]) {
  options = [[FIRVisionOnDeviceAutoMLImageLabelerOptions alloc] initWithRemoteModel:remoteModel];
} else {
  options = [[FIRVisionOnDeviceAutoMLImageLabelerOptions alloc] initWithLocalModel:localModel];
}
options.confidenceThreshold = 0.0f;  // Evaluate your model in the Firebase console
                                     // to determine an appropriate value.
FIRVisionImageLabeler *labeler = [[FIRVision vision] onDeviceAutoMLImageLabelerWithOptions:options];

Si solo tienes un modelo alojado de forma remota, debes inhabilitar la funcionalidad relacionada con el modelo, por ejemplo, oculta o inhabilita parte de tu IU, hasta que confirmes que el modelo se descargó.

Puedes obtener el estado de descarga del modelo si adjuntas observadores al Centro de notificaciones predeterminado. Asegúrate de utilizar una referencia débil para self en el bloque de observador, ya que las descargas pueden demorar un tiempo y el objeto de origen se puede liberar antes de que finalice la descarga. Por ejemplo:

Swift

NotificationCenter.default.addObserver(
    forName: .firebaseMLModelDownloadDidSucceed,
    object: nil,
    queue: nil
) { [weak self] notification in
    guard let strongSelf = self,
        let userInfo = notification.userInfo,
        let model = userInfo[ModelDownloadUserInfoKey.remoteModel.rawValue]
            as? RemoteModel,
        model.name == "your_remote_model"
        else { return }
    // The model was downloaded and is available on the device
}

NotificationCenter.default.addObserver(
    forName: .firebaseMLModelDownloadDidFail,
    object: nil,
    queue: nil
) { [weak self] notification in
    guard let strongSelf = self,
        let userInfo = notification.userInfo,
        let model = userInfo[ModelDownloadUserInfoKey.remoteModel.rawValue]
            as? RemoteModel
        else { return }
    let error = userInfo[ModelDownloadUserInfoKey.error.rawValue]
    // ...
}

Objective-C

__weak typeof(self) weakSelf = self;

[NSNotificationCenter.defaultCenter
    addObserverForName:FIRModelDownloadDidSucceedNotification
                object:nil
                 queue:nil
            usingBlock:^(NSNotification *_Nonnull note) {
              if (weakSelf == nil | note.userInfo == nil) {
                return;
              }
              __strong typeof(self) strongSelf = weakSelf;

              FIRRemoteModel *model = note.userInfo[FIRModelDownloadUserInfoKeyRemoteModel];
              if ([model.name isEqualToString:@"your_remote_model"]) {
                // The model was downloaded and is available on the device
              }
            }];

[NSNotificationCenter.defaultCenter
    addObserverForName:FIRModelDownloadDidFailNotification
                object:nil
                 queue:nil
            usingBlock:^(NSNotification *_Nonnull note) {
              if (weakSelf == nil | note.userInfo == nil) {
                return;
              }
              __strong typeof(self) strongSelf = weakSelf;

              NSError *error = note.userInfo[FIRModelDownloadUserInfoKeyError];
            }];

2. Prepara la imagen de entrada

Luego, para cada imagen que quieras etiquetar, crea un objeto VisionImage con una de las opciones que se describen en esta sección y pásalo a una instancia de VisionImageLabeler (que se describe en la sección siguiente).

Crea un objeto VisionImage mediante una UIImage o CMSampleBufferRef.

Para usar una UIImage, debes hacer lo siguiente:

  1. Si es necesario, rota la imagen para que la propiedad imageOrientation sea .up.
  2. Crea un objeto VisionImage mediante una UIImage que se haya rotado adecuadamente. No especifiques los metadatos de rotación. Se debe usar el valor predeterminado .topLeft.

    Swift

    let image = VisionImage(image: uiImage)

    Objective-C

    FIRVisionImage *image = [[FIRVisionImage alloc] initWithImage:uiImage];

Para usar una CMSampleBufferRef, debes hacer lo siguiente:

  1. Crea un objeto VisionImageMetadata que especifique la orientación de los datos de la imagen contenidos en el búfer CMSampleBufferRef.

    Para obtener la orientación de la imagen, haz lo siguiente:

    Swift

    func imageOrientation(
        deviceOrientation: UIDeviceOrientation,
        cameraPosition: AVCaptureDevice.Position
        ) -> VisionDetectorImageOrientation {
        switch deviceOrientation {
        case .portrait:
            return cameraPosition == .front ? .leftTop : .rightTop
        case .landscapeLeft:
            return cameraPosition == .front ? .bottomLeft : .topLeft
        case .portraitUpsideDown:
            return cameraPosition == .front ? .rightBottom : .leftBottom
        case .landscapeRight:
            return cameraPosition == .front ? .topRight : .bottomRight
        case .faceDown, .faceUp, .unknown:
            return .leftTop
        }
    }

    Objective-C

    - (FIRVisionDetectorImageOrientation)
        imageOrientationFromDeviceOrientation:(UIDeviceOrientation)deviceOrientation
                               cameraPosition:(AVCaptureDevicePosition)cameraPosition {
      switch (deviceOrientation) {
        case UIDeviceOrientationPortrait:
          if (cameraPosition == AVCaptureDevicePositionFront) {
            return FIRVisionDetectorImageOrientationLeftTop;
          } else {
            return FIRVisionDetectorImageOrientationRightTop;
          }
        case UIDeviceOrientationLandscapeLeft:
          if (cameraPosition == AVCaptureDevicePositionFront) {
            return FIRVisionDetectorImageOrientationBottomLeft;
          } else {
            return FIRVisionDetectorImageOrientationTopLeft;
          }
        case UIDeviceOrientationPortraitUpsideDown:
          if (cameraPosition == AVCaptureDevicePositionFront) {
            return FIRVisionDetectorImageOrientationRightBottom;
          } else {
            return FIRVisionDetectorImageOrientationLeftBottom;
          }
        case UIDeviceOrientationLandscapeRight:
          if (cameraPosition == AVCaptureDevicePositionFront) {
            return FIRVisionDetectorImageOrientationTopRight;
          } else {
            return FIRVisionDetectorImageOrientationBottomRight;
          }
        default:
          return FIRVisionDetectorImageOrientationTopLeft;
      }
    }

    Luego crea el objeto de metadatos de esta manera:

    Swift

    let cameraPosition = AVCaptureDevice.Position.back  // Set to the capture device you used.
    let metadata = VisionImageMetadata()
    metadata.orientation = imageOrientation(
        deviceOrientation: UIDevice.current.orientation,
        cameraPosition: cameraPosition
    )

    Objective-C

    FIRVisionImageMetadata *metadata = [[FIRVisionImageMetadata alloc] init];
    AVCaptureDevicePosition cameraPosition =
        AVCaptureDevicePositionBack;  // Set to the capture device you used.
    metadata.orientation =
        [self imageOrientationFromDeviceOrientation:UIDevice.currentDevice.orientation
                                     cameraPosition:cameraPosition];
  2. Crea un objeto VisionImage a través del objeto CMSampleBufferRef y los metadatos de rotación:

    Swift

    let image = VisionImage(buffer: sampleBuffer)
    image.metadata = metadata

    Objective-C

    FIRVisionImage *image = [[FIRVisionImage alloc] initWithBuffer:sampleBuffer];
    image.metadata = metadata;

3. Ejecuta el etiquetador de imágenes

Para etiquetar objetos de una imagen, pasa el objeto VisionImage al método process() de VisionImageLabeler:

Swift

labeler.process(image) { labels, error in
    guard error == nil, let labels = labels else { return }

    // Task succeeded.
    // ...
}

Objective-C

[labeler
    processImage:image
      completion:^(NSArray<FIRVisionImageLabel *> *_Nullable labels, NSError *_Nullable error) {
        if (error != nil || labels == nil) {
          return;
        }

        // Task succeeded.
        // ...
      }];

Si el etiquetado de imágenes se ejecuta correctamente, se pasará un arreglo de objetos VisionImageLabel al controlador de finalización. De cada objeto, puedes obtener la información sobre una característica reconocida en la imagen.

Por ejemplo:

Swift

for label in labels {
    let labelText = label.text
    let confidence = label.confidence
}

Objective-C

for (FIRVisionImageLabel *label in labels) {
  NSString *labelText = label.text;
  NSNumber *confidence = label.confidence;
}

Sugerencias para mejorar el rendimiento en tiempo real

  • Regula las llamadas al detector. Si hay un fotograma de video nuevo disponible mientras se ejecuta el detector, ignora ese fotograma.
  • Si estás usando la salida del detector para superponer gráficos en la imagen de entrada, primero obtén el resultado de la detección de ML Kit y, luego, procesa la imagen y la superposición en un solo paso. De esta manera, procesas la superficie de visualización solo una vez por cada fotograma de entrada. Consulta las clases previewOverlayView y FIRDetectionOverlayView en la app de muestra para ver un ejemplo.