Rotular imagens com um modelo personalizado no iOS

É possível usar o Kit de ML para reconhecer entidades em uma imagem e rotulá-las. Essa API é compatível com uma ampla variedade de modelos personalizados de classificação de imagens. Consulte Modelos personalizados com Kit de ML para ver orientações sobre requisitos de compatibilidade de modelo, onde encontrar modelos pré-treinados e como treinar seus próprios modelos.

Há duas maneiras de integrar um modelo personalizado. Você pode agrupar o modelo colocando-o na pasta de recursos do app ou fazendo o download dele dinamicamente do Firebase. A tabela a seguir compara as duas opções.

Modelo agrupado Modelo hospedado
O modelo faz parte do APK do seu app, o que aumenta o tamanho dele. O modelo não faz parte do APK. Ele é hospedado pelo upload para o Firebase Machine Learning.
O modelo estará disponível imediatamente, mesmo quando o dispositivo Android estiver off-line O download do modelo é feito sob demanda
Não é necessário ter um projeto do Firebase Requer um projeto do Firebase
É necessário publicar o app novamente para atualizar o modelo Enviar atualizações do modelo sem republicar o app
Nenhum teste A/B integrado Teste A/B fácil com a Configuração remota do Firebase.

Faça um teste

Antes de começar

  1. Inclua as bibliotecas do kit de ML no seu Podfile:

    Para empacotar um modelo e seu app:

    pod 'GoogleMLKit/ImageLabelingCustom', '3.2.0'
    

    Para fazer o download dinâmico de um modelo do Firebase, adicione a dependência LinkFirebase:

    pod 'GoogleMLKit/ImageLabelingCustom', '3.2.0'
    pod 'GoogleMLKit/LinkFirebase', '3.2.0'
    
  2. Depois de instalar ou atualizar os pods do projeto, abra o projeto do Xcode usando o .xcworkspace. O Kit de ML é compatível com a versão 13.2.1 ou mais recente do Xcode.

  3. Se você quiser fazer o download de um modelo, adicione o Firebase ao seu projeto do iOS, caso ainda não tenha feito isso. Essa etapa não é necessária para agrupar o modelo.

1. Carregar o modelo

Configurar uma fonte de modelo local

Para agrupar o modelo e o aplicativo, siga estas etapas:

  1. Copie o arquivo de modelo (geralmente terminando em .tflite ou .lite) para o projeto Xcode, selecionando Copy bundle resources ao fazer isso. O arquivo de modelo será incluído no pacote de apps e estará disponível para o Kit de ML.

  2. Crie o objeto LocalModel, especificando o caminho para o arquivo de modelo:

    Swift

    let localModel = LocalModel(path: localModelFilePath)

    Objective-C

    MLKLocalModel *localModel =
        [[MLKLocalModel alloc] initWithPath:localModelFilePath];

Configurar uma fonte de modelos hospedada no Firebase

Para usar o modelo hospedado remotamente, crie um objeto RemoteModel, especificando o nome que você atribuiu ao modelo quando o publicou:

Swift

let firebaseModelSource = FirebaseModelSource(
    name: "your_remote_model") // The name you assigned in
                               // the Firebase console.
let remoteModel = CustomRemoteModel(remoteModelSource: firebaseModelSource)

Objective-C

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

Em seguida, inicie a tarefa de download do modelo, especificando as condições sob as quais você quer permitir o download. Se o modelo não estiver no dispositivo ou se uma versão mais recente do modelo estiver disponível, a tarefa fará o download do modelo de forma assíncrona do Firebase:

Swift

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

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

Objective-C

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

NSProgress *downloadProgress =
    [[MLKModelManager modelManager] downloadModel:remoteModel
                                       conditions:downloadConditions];

Muitos apps iniciam a tarefa de download no código de inicialização, mas você pode fazer isso a qualquer momento antes de precisar usar o modelo.

Configurar o rotulador de imagens

Depois de configurar as origens do modelo, crie um objeto ImageLabeler usando uma delas.

As seguintes opções estão disponíveis:

Opções
confidenceThreshold

Pontuação de confiança mínima dos rótulos detectados. Se não for definido, o limite do classificador especificado pelos metadados do modelo será usado. Se o modelo não contiver metadados ou os metadados não especificarem um limite de classificador, será usado um limite padrão de 0,0.

maxResultCount

Número máximo de rótulos a serem retornados. Se não for definido, o valor padrão de 10 será usado.

Se você tiver apenas um modelo agrupado localmente, basta criar um rotulador usando o objeto LocalModel:

Swift

let options = CustomImageLabelerOptions(localModel: localModel)
options.confidenceThreshold = NSNumber(value: 0.0)
let imageLabeler = ImageLabeler.imageLabeler(options: options)

Objective-C

MLKCustomImageLabelerOptions *options =
    [[MLKCustomImageLabelerOptions alloc] initWithLocalModel:localModel];
options.confidenceThreshold = @(0.0);
MLKImageLabeler *imageLabeler =
    [MLKImageLabeler imageLabelerWithOptions:options];

Se você tiver um modelo hospedado remotamente, será necessário verificar se foi feito o download dele antes de executá-lo. É possível verificar o status da tarefa de download do modelo usando o método isModelDownloaded(remoteModel:) do gerenciador de modelos.

Embora isso só precise ser confirmado antes da execução do rotulador, se você tiver um modelo hospedado remotamente e um modelo empacotado localmente, talvez seja útil realizar essa verificação ao instanciar o ImageLabeler: criar um rotulador usando o modelo remoto se ele tiver sido transferido por download e, caso contrário, usando o modelo local.

Swift

var options: CustomImageLabelerOptions!
if (ModelManager.modelManager().isModelDownloaded(remoteModel)) {
  options = CustomImageLabelerOptions(remoteModel: remoteModel)
} else {
  options = CustomImageLabelerOptions(localModel: localModel)
}
options.confidenceThreshold = NSNumber(value: 0.0)
let imageLabeler = ImageLabeler.imageLabeler(options: options)

Objective-C

MLKCustomImageLabelerOptions *options;
if ([[MLKModelManager modelManager] isModelDownloaded:remoteModel]) {
  options = [[MLKCustomImageLabelerOptions alloc] initWithRemoteModel:remoteModel];
} else {
  options = [[MLKCustomImageLabelerOptions alloc] initWithLocalModel:localModel];
}
options.confidenceThreshold = @(0.0);
MLKImageLabeler *imageLabeler =
    [MLKImageLabeler imageLabelerWithOptions:options];

Se você tiver apenas um modelo hospedado remotamente, desative o recurso relacionado ao modelo (por exemplo, ocultando ou esmaecendo parte da IU) até confirmar que o download do modelo foi concluído.

Para ver o status de download do modelo, anexe observadores à Central de notificações padrão. Certifique-se de usar uma referência fraca para self no bloco de observadores, porque os downloads podem demorar algum tempo e o objeto de origem poderá ser liberado quando o download for concluído. Exemplo:

Swift

NotificationCenter.default.addObserver(
    forName: .mlkitModelDownloadDidSucceed,
    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: .mlkitModelDownloadDidFail,
    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:MLKModelDownloadDidSucceedNotification
                object:nil
                 queue:nil
            usingBlock:^(NSNotification *_Nonnull note) {
              if (weakSelf == nil | note.userInfo == nil) {
                return;
              }
              __strong typeof(self) strongSelf = weakSelf;

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

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

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

2. Preparar a imagem de entrada

Crie um objeto VisionImage usando um UIImage ou um CMSampleBuffer.

Se você usa um UIImage, siga estas etapas:

  • Crie um objeto VisionImage com o UIImage. Especifique o .orientation correto.

    Swift

    let image = VisionImage(image: UIImage)
    visionImage.orientation = image.imageOrientation

    Objective-C

    MLKVisionImage *visionImage = [[MLKVisionImage alloc] initWithImage:image];
    visionImage.orientation = image.imageOrientation;

Se você usa um CMSampleBuffer, siga estas etapas:

  • Especifique a orientação dos dados da imagem contidos em CMSampleBuffer.

    Para ver a orientação da imagem:

    Swift

    func imageOrientation(
      deviceOrientation: UIDeviceOrientation,
      cameraPosition: AVCaptureDevice.Position
    ) -> UIImage.Orientation {
      switch deviceOrientation {
      case .portrait:
        return cameraPosition == .front ? .leftMirrored : .right
      case .landscapeLeft:
        return cameraPosition == .front ? .downMirrored : .up
      case .portraitUpsideDown:
        return cameraPosition == .front ? .rightMirrored : .left
      case .landscapeRight:
        return cameraPosition == .front ? .upMirrored : .down
      case .faceDown, .faceUp, .unknown:
        return .up
      }
    }
          

    Objective-C

    - (UIImageOrientation)
      imageOrientationFromDeviceOrientation:(UIDeviceOrientation)deviceOrientation
                             cameraPosition:(AVCaptureDevicePosition)cameraPosition {
      switch (deviceOrientation) {
        case UIDeviceOrientationPortrait:
          return cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationLeftMirrored
                                                                : UIImageOrientationRight;
    
        case UIDeviceOrientationLandscapeLeft:
          return cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationDownMirrored
                                                                : UIImageOrientationUp;
        case UIDeviceOrientationPortraitUpsideDown:
          return cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationRightMirrored
                                                                : UIImageOrientationLeft;
        case UIDeviceOrientationLandscapeRight:
          return cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationUpMirrored
                                                                : UIImageOrientationDown;
        case UIDeviceOrientationUnknown:
        case UIDeviceOrientationFaceUp:
        case UIDeviceOrientationFaceDown:
          return UIImageOrientationUp;
      }
    }
          
  • Crie um objeto VisionImage usando o objeto CMSampleBuffer e a orientação:

    Swift

    let image = VisionImage(buffer: sampleBuffer)
    image.orientation = imageOrientation(
      deviceOrientation: UIDevice.current.orientation,
      cameraPosition: cameraPosition)

    Objective-C

     MLKVisionImage *image = [[MLKVisionImage alloc] initWithBuffer:sampleBuffer];
     image.orientation =
       [self imageOrientationFromDeviceOrientation:UIDevice.currentDevice.orientation
                                    cameraPosition:cameraPosition];

3. Executar o rotulador de imagens

Para rotular objetos em uma imagem, transmita o objeto image para o método process() do ImageLabeler.

De forma assíncrona:

Swift

imageLabeler.process(image) { labels, error in
    guard error == nil, let labels = labels, !labels.isEmpty else {
        // Handle the error.
        return
    }
    // Show results.
}

Objective-C

[imageLabeler
    processImage:image
      completion:^(NSArray *_Nullable labels,
                   NSError *_Nullable error) {
        if (label.count == 0) {
            // Handle the error.
            return;
        }
        // Show results.
     }];

De forma síncrona:

Swift

var labels: [ImageLabel]
do {
    labels = try imageLabeler.results(in: image)
} catch let error {
    // Handle the error.
    return
}
// Show results.

Objective-C

NSError *error;
NSArray *labels =
    [imageLabeler resultsInImage:image error:&error];
// Show results or handle the error.

4. Receber informações sobre entidades rotuladas

Se a operação de rotulagem de imagem for bem-sucedida, ela retornará uma matriz de ImageLabel. Cada ImageLabel representa algo que foi rotulado na imagem. Você pode ver a descrição de texto de cada rótulo (se disponível nos metadados do arquivo de modelo do TensorFlow Lite), a pontuação de confiança e o índice. Exemplo:

Swift

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

Objective-C

for (MLKImageLabel *label in labels) {
  NSString *labelText = label.text;
  float confidence = label.confidence;
  NSInteger index = label.index;
}

Dicas para melhorar o desempenho em tempo real

Se você quiser rotular imagens em um aplicativo em tempo real, siga estas diretrizes para ter as melhores taxas de frames:

  • Para processar frames de vídeo, use a API síncrona results(in:) do detector. Chame esse método da função captureOutput(_, didOutput:from:) de AVCaptureVideoDataOutputSampleBufferDelegate para receber resultados de maneira síncrona do frame de vídeo especificado. Mantenha o alwaysDiscardsLateVideoFrames de AVCaptureVideoDataOutput como true para limitar as chamadas para o detector. Se um novo frame de vídeo for disponibilizado enquanto o detector estiver em execução, ele será descartado.
  • Se você usar a saída do detector para sobrepor elementos gráficos na imagem de entrada, primeiro acesse o resultado do Kit de ML e, em seguida, renderize a imagem e a sobreposição em uma única etapa. Ao fazer isso, você renderiza a superfície de exibição apenas uma vez para cada frame de entrada processado. Consulte updatePreviewOverlayViewWithLastFrame no exemplo do guia de início rápido do Kit de ML para ver um exemplo.