Detectar e rastrear objetos com o Kit de ML no iOS

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

Quando você transmite imagens do Kit de ML, ele retorna, para cada imagem, uma lista de até cinco objetos detectados e a posição deles na imagem. Cada objeto que você detecta em streams de vídeo tem um ID que pode ser rastreado nas imagens. É possível também, como opção, ativar a classificação abrangente de objetos, que marca objetos com descrições de categorias amplas.

Antes de começar

  1. Se você ainda não adicionou o Firebase ao seu app, siga as etapas no guia de iniciação.
  2. Inclua as bibliotecas do Kit de ML no seu Podfile:
    pod 'Firebase/MLVision', '6.25.0'
    pod 'Firebase/MLVisionObjectDetection', '6.25.0'
    
    Depois de instalar ou atualizar os pods do projeto, abra o projeto do Xcode usando o .xcworkspace.
  3. Importe o Firebase para seu app:

    Swift

    import Firebase

    Objective-C

    @import Firebase;

1. Configurar o detector de objetos

Para começar a detectar e rastrear objetos, primeiro crie uma instância de VisionObjectDetector, especificando opcionalmente as configurações do detector que você quer alterar do padrão.

  1. Configure o detector de objetos para seu caso de uso com um objeto VisionObjectDetectorOptions. É possível alterar as seguintes configurações:

    Configurações do detector de objetos
    Modo de detecção .stream (padrão) | .singleImage

    No modo de streaming (padrão), o detector de objetos é executado com baixa latência, mas pode produzir resultados incompletos, como caixas delimitadoras ou categoria não especificadas, nas primeiras chamadas do detector. Além disso, no modo de streaming, o detector atribui IDs de rastreamento a objetos, que podem ser usados para rastrear objetos em frames. Use esse modo quando quiser rastrear objetos ou quando a baixa latência for importante, como ao processar streams de vídeo em tempo real.

    No modo de imagem única, o detector de objetos aguarda até que a caixa delimitadora e a categoria (se você tiver ativado a classificação) de um objeto detectado estejam disponíveis antes de retornar um resultado. Como resultado, a latência de detecção é potencialmente maior. Além disso, no modo de imagem única, os IDs de acompanhamento não são atribuídos. Use esse modo se a latência não for essencial e você não quiser lidar com resultados parciais.

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

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

    Classificar objetos false (padrão) | true

    Se for preciso ou não classificar os objetos detectados em categorias abrangentes. Quando ativado, o detector de objetos os classifica nas seguintes categorias: artigos de moda, alimentos, artigos domésticos, locais, plantas e desconhecido.

    A API de detecção e rastreamento de objetos é otimizada para os dois casos de uso principais a seguir:

    • Detecção ao vivo e rastreamento do objeto mais proeminente no visor da câmera
    • 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 = VisionObjectDetectorOptions()
    options.detectorMode = .stream
    options.shouldEnableMultipleObjects = false
    options.shouldEnableClassification = true  // Optional
    
    // Multiple object detection in static images
    let options = VisionObjectDetectorOptions()
    options.detectorMode = .singleImage
    options.shouldEnableMultipleObjects = true
    options.shouldEnableClassification = true  // Optional
    

    Objective-C

    // Live detection and tracking
    FIRVisionObjectDetectorOptions *options = [[FIRVisionObjectDetectorOptions alloc] init];
    options.detectorMode = FIRVisionObjectDetectorModeStream;
    options.shouldEnableMultipleObjects = NO;
    options.shouldEnableClassification = YES;  // Optional
    
    // Multiple object detection in static images
    FIRVisionObjectDetectorOptions *options = [[FIRVisionObjectDetectorOptions alloc] init];
    options.detectorMode = FIRVisionObjectDetectorModeSingleImage;
    options.shouldEnableMultipleObjects = YES;
    options.shouldEnableClassification = YES;  // Optional
    
  2. Receba uma instância de FirebaseVisionObjectDetector:

    Swift

    let objectDetector = Vision.vision().objectDetector()
    
    // Or, to change the default settings:
    let objectDetector = Vision.vision().objectDetector(options: options)
    

    Objective-C

    FIRVisionObjectDetector *objectDetector = [[FIRVision vision] objectDetector];
    
    // Or, to change the default settings:
    FIRVisionObjectDetector *objectDetector = [[FIRVision vision] objectDetectorWithOptions:options];
    

2. Executar o detector de objetos

Para detectar e rastrear objetos, siga as etapas abaixo para cada imagem ou frame de vídeo. Se você tiver ativado o modo de stream, precisará criar objetos VisionImage a partir de CMSampleBufferRef.

  1. Crie um objeto VisionImage usando um UIImage ou um CMSampleBufferRef.

    Para usar um UIImage:

    1. Se necessário, gire a imagem para que a propriedade imageOrientation seja .up.
    2. Crie um objeto VisionImage usando a UIImage com a rotação correta. Não especifique metadados de rotação: o valor padrão, .topLeft, precisa ser usado.

      Swift

      let image = VisionImage(image: uiImage)

      Objective-C

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

    Para usar um CMSampleBufferRef:

    1. Crie um objeto VisionImageMetadata que especifique a orientação dos dados da imagem contidos no buffer CMSampleBufferRef.

      Para ver a orientação da imagem:

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

      Em seguida, crie o objeto de metadados:

      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. Crie um objeto VisionImage usando o objeto CMSampleBufferRef e os metadados de rotação:

      Swift

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

      Objective-C

      FIRVisionImage *image = [[FIRVisionImage alloc] initWithBuffer:sampleBuffer];
      image.metadata = metadata;
  2. Passe o VisionImage para um dos métodos de processamento de imagem do detector de objetos. É possível usar o método process(image:) assíncrono ou o método results() síncrono.

    Para detectar objetos de modo assíncrono:

    Swift

    objectDetector.process(image) { detectedObjects, error in
      guard error == nil else {
        // Error.
        return
      }
      guard let detectedObjects = detectedObjects, !detectedObjects.isEmpty else {
        // No objects detected.
        return
      }
    
      // Success. Get object info here.
      // ...
    }
    

    Objective-C

    [objectDetector processImage:image
                      completion:^(NSArray<FIRVisionObject *> * _Nullable objects,
                                   NSError * _Nullable error) {
                        if (error == nil) {
                          return;
                        }
                        if (objects == nil | objects.count == 0) {
                          // No objects detected.
                          return;
                        }
    
                        // Success. Get object info here.
                        // ...
                      }];
    

    Para detectar objetos de modo síncrono:

    Swift

    var results: [VisionObject]? = nil
    do {
      results = try objectDetector.results(in: image)
    } catch let error {
      print("Failed to detect object with error: \(error.localizedDescription).")
      return
    }
    guard let detectedObjects = results, !detectedObjects.isEmpty else {
      print("Object detector returned no results.")
      return
    }
    
    // ...
    

    Objective-C

    NSError *error;
    NSArray<FIRVisionObject *> *objects = [objectDetector resultsInImage:image
                                                                   error:&error];
    if (error == nil) {
      return;
    }
    if (objects == nil | objects.count == 0) {
      // No objects detected.
      return;
    }
    
    // Success. Get object info here.
    // ...
    
  3. Se a chamada para o processador de imagem for bem-sucedida, ela transmitirá uma lista de VisionObject para o gerenciador de conclusão ou retornará a lista, caso você tenha chamado o método assíncrono ou síncrono.

    Cada VisionObject contém as seguintes propriedades:

    frame Um CGRect que indica a posição do objeto na imagem.
    trackingID Um número inteiro que identifica o objeto nas imagens. Nulo no modo de imagem única.
    classificationCategory A categoria abrangente do objeto. Se o detector de objetos não tiver a classificação ativada, isso será sempre .unknown.
    confidence O nível de confiança da classificação do objeto. Se o detector de objetos não tiver a classificação ativada ou o objeto for classificado como desconhecido, isso será nil.

    Swift

    // detectedObjects contains one item if multiple object detection wasn't enabled.
    for obj in detectedObjects {
      let bounds = obj.frame
      let id = obj.trackingID
    
      // If classification was enabled:
      let category = obj.classificationCategory
      let confidence = obj.confidence
    }
    

    Objective-C

    // The list of detected objects contains one item if multiple
    // object detection wasn't enabled.
    for (FIRVisionObject *obj in objects) {
      CGRect bounds = obj.frame;
      if (obj.trackingID) {
        NSInteger id = obj.trackingID.integerValue;
      }
    
      // If classification was enabled:
      FIRVisionObjectCategory category = obj.classificationCategory;
      float confidence = obj.confidence.floatValue;
    }
    

Como melhorar a usabilidade e o desempenho

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

  • A detecção bem-sucedida de objetos depende da complexidade visual do objeto. Objetos com um pequeno número de recursos visuais podem precisar ocupar uma parte maior da imagem a ser detectada. Forneça aos usuários orientações sobre como capturar entradas que funcionem bem com o tipo de objeto que você quer detectar.
  • Ao usar a classificação, se você quiser detectar objetos que não se enquadrem nas categorias suportadas, implemente o tratamento especial para objetos desconhecidos.

Além disso, confira o conjunto de [app de demonstração do Material Design do Kit de ML][showcase-link]{: .external} e a coleção Padrões de recursos de machine learning do Material Design.

Ao usar o modo de streaming em um aplicativo em tempo real, siga estas diretrizes para alcançar as melhores taxas de frames:

  • Não use a detecção de vários objetos no modo de streaming, porque a maioria dos dispositivos não será capaz de produzir taxas de frames adequadas.

  • Desative a classificação se ela não for necessária.

  • Limite as chamadas ao detector. Se um novo frame de vídeo ficar disponível durante a execução do detector, descarte esse frame.
  • Se você estiver usando a saída do detector para sobrepor elementos gráficos na imagem de entrada, primeiro acesse o resultado do Kit de ML. Em seguida, renderize a imagem e faça a sobreposição de uma só vez. Ao fazer isso, você renderiza a superfície de exibição apenas uma vez para cada frame de entrada. Consulte as classes previewOverlayView e FIRDetectionOverlayView no app de exemplo da demonstração.