Detecte, rastreie e classifique objetos com um modelo de classificação personalizado no iOS

É possível usar o Kit de ML para detectar e rastrear objetos em frames de vídeo sucessivos.

Quando você transmite uma imagem para o Kit de ML, ele detecta até cinco objetos na imagem junto com a posição de cada objeto na imagem. Ao detectar objetos em streams de vídeo, cada objeto tem um ID exclusivo que pode ser usado para rastrear o objeto de frame a frame.

É possível usar um modelo de classificação de imagens personalizado para classificar os objetos detectado. Consulte Modelos personalizados com o Kit de ML para orientações sobre requisitos de compatibilidade de modelos, onde encontrar modelos pré-treinados, e como treinar seus próprios modelos.

Há duas maneiras de integrar um modelo personalizado. É possível agrupar o modelo colocá-lo na pasta de recursos do app ou fazer o download dele dinamicamente do Firebase. A tabela a seguir compara as duas opções.

Modelo em pacote Modelo hospedado
O modelo faz parte do arquivo .ipa do app, que aumenta seu tamanho. O modelo não faz parte do arquivo .ipa do app. É hospedado fazendo upload para Machine Learning do Firebase.
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 republicar o app para atualizar o modelo Enviar atualizações do modelo sem republicar o app
Sem testes A/B integrados 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 agrupar um modelo e seu app:

    pod 'GoogleMLKit/ObjectDetectionCustom', '3.2.0'
    

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

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

  3. Se você quiser fazer o download de um modelo, verifique se adicione o Firebase ao seu projeto do iOS, caso ainda não tenha feito isso. Isso não é necessário quando você agrupa o um modelo de machine learning.

1. Carregar o modelo

Configurar uma fonte de modelo local

Para agrupar o modelo e o app, faça o seguinte:

  1. Copie o arquivo do modelo (geralmente terminando em .tflite ou .lite) para seu Xcode projeto. Lembre-se de selecionar Copy bundle resources ao fazer isso. A do 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 modelo hospedada no Firebase

Para usar o modelo hospedado remotamente, crie um objeto CustomRemoteModel, 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 do qual você quer permitir o download. Se o modelo não estiver no dispositivo ou se um modelo mais recente versão do modelo estiver disponível, a tarefa fará o download do arquivo 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.

2. Configurar o detector de objetos

Depois de configurar as origens do modelo, configure o detector de objetos do seu caso de uso com um objeto CustomObjectDetectorOptions. É possível alterar seguintes configurações:

Configurações do detector de objetos
Modo de detecção STREAM_MODE (padrão) | SINGLE_IMAGE_MODE

Em STREAM_MODE (padrão), o detector de objetos é executado com baixa latência, mas pode produzir resultados incompletos, como caixas delimitadoras ou rótulos de categoria não especificados) nos primeiros do detector. Além disso, em STREAM_MODE, o detector atribui IDs de rastreamento a objetos, que você pode usar para rastrear objetos em frames. Use esse modo quando quiser monitorar objetos ou quando a baixa latência for importante, como ao processar streams de vídeo em tempo real.

Em SINGLE_IMAGE_MODE, o detector de objetos retorna o resultado após a caixa delimitadora do objeto ser determinada. Se você ativar a classificação, ela retorna o resultado após o delimitador e o rótulo da categoria estão disponíveis. Como consequência, de detecção é potencialmente maior. Além disso, em SINGLE_IMAGE_MODE, os IDs de acompanhamento não foram atribuídos. Usar use esse modo se a latência não for essencial e você não quiser resultados parciais.

Detectar e rastrear vários objetos false (padrão) | true

Se deve detectar e rastrear até cinco objetos ou apenas os mais objeto proeminente (padrão).

Classificar objetos false (padrão) | true

Se os objetos detectados são ou não classificados usando os atributos modelo de classificador personalizado. Para usar sua classificação personalizada modelo, defina-o como true.

Limite de confiança de classificação

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

Máximo de rótulos por objeto

Número máximo de rótulos por objeto que o detector voltar. Se não for definido, o valor padrão de 10 será usado.

Se você tiver apenas um modelo agrupado localmente, basta criar um detector de objetos seu objeto LocalModel:

Swift

let options = CustomObjectDetectorOptions(localModel: localModel)
options.detectorMode = .singleImage
options.shouldEnableClassification = true
options.shouldEnableMultipleObjects = true
options.classificationConfidenceThreshold = NSNumber(value: 0.5)
options.maxPerObjectLabelCount = 3

Objective-C

MLKCustomObjectDetectorOptions *options =
    [[MLKCustomObjectDetectorOptions alloc] initWithLocalModel:localModel];
options.detectorMode = MLKObjectDetectorModeSingleImage;
options.shouldEnableClassification = YES;
options.shouldEnableMultipleObjects = YES;
options.classificationConfidenceThreshold = @(0.5);
options.maxPerObjectLabelCount = 3;

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

Embora isso só precise ser confirmado antes de executar o detector de objetos, se você tem um modelo hospedado remotamente e um modelo agrupado localmente, isso pode tornar para fazer essa verificação ao instanciar o ObjectDetector: crie um do modelo remoto, caso tenha sido transferido por download, e do modelo local caso contrário.

Swift

var options: CustomObjectDetectorOptions!
if (ModelManager.modelManager().isModelDownloaded(remoteModel)) {
  options = CustomObjectDetectorOptions(remoteModel: remoteModel)
} else {
  options = CustomObjectDetectorOptions(localModel: localModel)
}
options.detectorMode = .singleImage
options.shouldEnableClassification = true
options.shouldEnableMultipleObjects = true
options.classificationConfidenceThreshold = NSNumber(value: 0.5)
options.maxPerObjectLabelCount = 3

Objective-C

MLKCustomObjectDetectorOptions *options;
if ([[MLKModelManager modelManager] isModelDownloaded:remoteModel]) {
  options = [[MLKCustomObjectDetectorOptions alloc] initWithRemoteModel:remoteModel];
} else {
  options = [[MLKCustomObjectDetectorOptions alloc] initWithLocalModel:localModel];
}
options.detectorMode = MLKObjectDetectorModeSingleImage;
options.shouldEnableClassification = YES;
options.shouldEnableMultipleObjects = YES;
options.classificationConfidenceThreshold = @(0.5);
options.maxPerObjectLabelCount = 3;

Se você tiver apenas um modelo hospedado remotamente, desative o recurso da interface, por exemplo, esmaecer ou ocultar parte da interface, até confirme se o download do modelo foi concluído.

É possível receber o status de download do modelo anexando observadores ao arquivo Central de Notificações. Use uma referência fraca a self no observador. já que os downloads podem levar algum tempo e o objeto de origem pode ser liberado no momento em que o download é 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];
            }];

A API de detecção e rastreamento de objetos é otimizada para esses dois principais casos:

  • Detecção ao vivo e rastreamento do objeto mais proeminente na câmera visor de fotos.
  • Detecção de vários objetos em uma imagem estática.

Para configurar a API para esses casos de uso:

Swift

// Live detection and tracking
let options = CustomObjectDetectorOptions(localModel: localModel)
options.shouldEnableClassification = true
options.maxPerObjectLabelCount = 3

// Multiple object detection in static images
let options = CustomObjectDetectorOptions(localModel: localModel)
options.detectorMode = .singleImage
options.shouldEnableMultipleObjects = true
options.shouldEnableClassification = true
options.maxPerObjectLabelCount = 3

Objective-C

// Live detection and tracking
MLKCustomObjectDetectorOptions *options =
    [[MLKCustomObjectDetectorOptions alloc] initWithLocalModel:localModel];
options.shouldEnableClassification = YES;
options.maxPerObjectLabelCount = 3;

// Multiple object detection in static images
MLKCustomObjectDetectorOptions *options =
    [[MLKCustomObjectDetectorOptions alloc] initWithLocalModel:localModel];
options.detectorMode = MLKObjectDetectorModeSingleImage;
options.shouldEnableMultipleObjects = YES;
options.shouldEnableClassification = YES;
options.maxPerObjectLabelCount = 3;

3. 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 no CMSampleBuffer:

    Para saber qual é 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 e orientação CMSampleBuffer:

    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];

4. Criar e executar o detector de objetos

  1. Crie um novo detector de objetos:

    Swift

    let objectDetector = ObjectDetector.objectDetector(options: options)

    Objective-C

    MLKObjectDetector *objectDetector = [MLKObjectDetector objectDetectorWithOptions:options];
  2. Em seguida, use o detector:

    De forma assíncrona:

    Swift

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

    Objective-C

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

    De forma síncrona:

    Swift

    var objects: [Object]
    do {
        objects = try objectDetector.results(in: image)
    } catch let error {
        // Handle the error.
        return
    }
    // Show results.

    Objective-C

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

5. Conseguir informações sobre os objetos rotulados

Se a chamada para o processador de imagem for bem-sucedida, ela passará uma lista de Objects para o gerenciador de conclusão ou retorna a lista, dependendo da independentemente de ter chamado o método assíncrono ou síncrono.

Cada Object contém as seguintes propriedades:

frame Um CGRect que indica a posição do objeto no imagem.
trackingID Um número inteiro que identifica o objeto entre imagens, ou "nil" em SINGLE_IMAGE_MODE.
labels
label.text A descrição do texto do rótulo. Retorna somente se o TensorFlow Os metadados do modelo Lite contêm descrições de rótulos.
label.index O índice do rótulo entre todos os rótulos suportados pelo classificador.
label.confidence O nível de confiança da classificação de objetos.

Swift

// objects contains one item if multiple object detection wasn't enabled.
for object in objects {
  let frame = object.frame
  let trackingID = object.trackingID
  let description = object.labels.enumerated().map { (index, label) in
    "Label \(index): \(label.text), \(label.confidence), \(label.index)"
  }.joined(separator: "\n")
}

Objective-C

// The list of detected objects contains one item if multiple object detection
// wasn't enabled.
for (MLKObject *object in objects) {
  CGRect frame = object.frame;
  NSNumber *trackingID = object.trackingID;
  for (MLKObjectLabel *label in object.labels) {
    NSString *labelString =
        [NSString stringWithFormat:@"%@, %f, %lu",
                                   label.text,
                                   label.confidence,
                                   (unsigned long)label.index];
  }
}

Como garantir uma ótima experiência do usuário

Para ter a melhor experiência do usuário, siga estas diretrizes no app:

  • A detecção bem-sucedida de objetos depende da complexidade visual do objeto. Em para serem detectados, os objetos com um pequeno número de recursos visuais podem precisar para ocupar uma parte maior da imagem. Você deve fornecer aos usuários orientação sobre capturando entradas que funcionam bem com o tipo de objetos que você quer detectar.
  • Na classificação, para detectar objetos que não se enquadram claramente nas categorias suportadas, implemente tratamento especial para objetos.

Além disso, confira a [app de demonstração do Kit de ML com Material Design][showcase-link]{: .external } e o Material Design Coleção de padrões para recursos com tecnologia de aprendizado de máquina.

Como melhorar o desempenho

Para usar a detecção de objetos em um aplicativo em tempo real, siga estas instruções diretrizes para obter as melhores taxas de quadros:

  • Ao usar o modo de streaming em um aplicativo em tempo real, não use diversos detecção de objetos, já que a maioria dos dispositivos não é capaz de produzir taxas de quadros adequadas.

  • Para processar frames de vídeo, use a API síncrona results(in:) do detector. Ligação esse método da AVCaptureVideoDataOutputSampleBufferDelegate captureOutput(_, didOutput:from:) para receber resultados do vídeo fornecido de forma síncrona frame. Manter de AVCaptureVideoDataOutput alwaysDiscardsLateVideoFrames como true para limitar as chamadas ao detector. Se uma nova frame de vídeo ficar disponível enquanto o detector estiver em execução, ele será descartado.
  • Se você usar a saída do detector para sobrepor elementos gráficos a imagem de entrada, primeiro acesse o resultado do Kit de ML e, em seguida, renderize a imagem e 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. Veja a classe updatePreviewOverlayViewWithLastFrame na amostra do guia de início rápido do Kit de ML.