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 nela junto com a posição de cada um. Ao detectar objetos em streams de vídeo, cada um tem um ID exclusivo que pode ser usado para rastrear o objeto de frame a frame.

É possível usar um modelo personalizado de classificação de imagens para classificar os objetos detectados. 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. Você pode empacotar o modelo colocando-o na pasta de recursos do app ou fazer o download dele dinamicamente do Cloud Storage. A tabela a seguir compara as duas opções.

Modelo agrupado Modelo hospedado
O modelo faz parte do arquivo .ipa do app, o que aumenta o tamanho dele. O modelo não faz parte do arquivo .ipa do seu app. Ele é hospedado com o upload para o Cloud Storage. Recomendamos usar o Cloud Storage para Firebase.
O modelo estará disponível imediatamente, mesmo quando o dispositivo Android estiver off-line O app precisa incluir código para fazer o download do modelo sob demanda.
Não é necessário ter um projeto do Firebase Requer um projeto do Firebase (se você estiver usando o Cloud Storage para Firebase).
Você precisa republicar o app para atualizar o modelo Enviar atualizações do modelo sem republicar o app
Sem teste A/B integrado Teste A/B 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:

    pod 'GoogleMLKit/ObjectDetectionCustom', '8.0.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 superior do Xcode.

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

1. Carregar o modelo

Configurar uma fonte de modelo local

Para agrupar o modelo e o app:

  1. Copie o arquivo de modelo (geralmente terminado em .tflite ou .lite) para seu projeto do 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 do modelo:

    Swift

    let localModel = LocalModel(path: localModelFilePath)

    Objective-C

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

Configurar uma fonte de modelo hospedada remotamente

Para usar o modelo hospedado remotamente, faça o download do arquivo modelo para o armazenamento local do dispositivo usando sua própria lógica de app e carregue-o como um modelo local. Recomendamos usar o Cloud Storage para Firebase para hospedar um modelo. Para detalhes da implementação, consulte o guia de migração do Firebase ML para o Cloud Storage.

2. Configurar o detector de objetos

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

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

No 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 categorias não especificados, nas primeiras chamadas do detector. Além disso, no STREAM_MODE, 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 SINGLE_IMAGE_MODE, o detector de objetos retorna o resultado depois que a caixa delimitadora do objeto é determinada. Se você também ativar a classificação, o resultado será retornado depois que a caixa delimitadora e o rótulo da categoria estiverem disponíveis. Como consequência, a latência de detecção pode ser maior. Além disso, no SINGLE_IMAGE_MODE, os IDs de rastreamento 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 é preciso ou não classificar os objetos detectados usando o modelo de classificador personalizado fornecido. Para usar seu modelo de classificação personalizada, defina isso como true.

Limite de confiança de classificação

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

Número máximo de rótulos por objeto

Número máximo de rótulos por objeto que o detector vai retornar. 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 usando o 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 foi feito o download dele antes de executá-lo.

Embora você só precise confirmar isso antes de executar o detector de objetos, se tiver um modelo hospedado remotamente e um agrupado localmente, talvez seja interessante realizar essa verificação ao instanciar o ObjectDetector: crie um detector do modelo remoto se ele tiver sido baixado e do modelo local caso contrário.

Swift

// Path where your download logic saves the model
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let localModelURL = documentDirectory.appendingPathComponent("my_remote_model.tflite")

let model: LocalModel
if FileManager.default.fileExists(atPath: localModelURL.path) {
  // Use the downloaded model
  model = LocalModel(path: localModelURL.path)
} else {
  // Fall back to bundled model
  guard let bundledModelPath = Bundle.main.path(forResource: "model", ofType: "tflite") else { return }
  model = LocalModel(path: bundledModelPath)
}

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

Objective-C

NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *localModelPath = [documentsDirectory stringByAppendingPathComponent:@"my_remote_model.tflite"];

MLKLocalModel *model;
if ([NSFileManager.defaultManager fileExistsAtPath:localModelPath]) {
  // Use the downloaded model
  model = [[MLKLocalModel alloc] initWithPath:localModelPath];
} else {
  // Fall back to bundled model
  NSString *bundledModelPath = [NSBundle.mainBundle pathForResource:@"model" ofType:@"tflite"];
  model = [[MLKLocalModel alloc] initWithPath:bundledModelPath];
}

MLKCustomObjectDetectorOptions *options = [[MLKCustomObjectDetectorOptions alloc] initWithLocalModel:model];
options.detectorMode = MLKObjectDetectorModeSingleImage;
options.shouldEnableClassification = YES;
options.shouldEnableMultipleObjects = YES;
options.classificationConfidenceThreshold = @(0.5);
options.maxPerObjectLabelCount = 3;
MLKObjectDetector *objectDetector = [MLKObjectDetector objectDetectorWithOptions: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.

Swift

let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let localModelURL = documentDirectory.appendingPathComponent("my_remote_model.tflite")
if FileManager.default.fileExists(atPath: localModelURL.path) {
  // Model is already cached, initialize immediately
  self.initializeDetector(with: localModelURL)
} else {
  // Model is not yet available, show loading UI and start download
  self.showLoadingUI()
  let storage = Storage.storage()
  let modelRef = storage.reference(forURL: "gs://YOUR_BUCKET/path/to/model.tflite")
  modelRef.write(toFile: localModelURL) { url, error in
    self.hideLoadingUI()
    if let error = error {
      // Handle download error
      self.showErrorUI()
    } else if let modelURL = url {
      // Download success, initialize detector
      self.initializeDetector(with: modelURL)
    }
  }
}

func initializeDetector(with modelURL: URL) {
  let localModel = LocalModel(path: modelURL.path)
  let options = CustomObjectDetectorOptions(localModel: localModel)
  options.detectorMode = .singleImage
  options.shouldEnableClassification = true
  options.shouldEnableMultipleObjects = true
  self.objectDetector = ObjectDetector.objectDetector(options: options)
  // Enable ML features in UI
  self.enableMLFeatures()
}

Objective-C

NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *localModelPath = [documentsDirectory stringByAppendingPathComponent:@"my_remote_model.tflite"];
NSURL *localModelURL = [NSURL fileURLWithPath:localModelPath];

if ([NSFileManager.defaultManager fileExistsAtPath:localModelPath]) {
  // Model is already cached, initialize immediately
  [self initializeDetectorWithURL:localModelURL];
} else {
  // Model is not yet available, show loading UI and start download
  [self showLoadingUI];

  FIRStorage *storage = [FIRStorage storage];
  FIRStorageReference *modelRef = [storage referenceForURL:@"gs://YOUR_BUCKET/path/to/model.tflite"];

  [modelRef writeToFile:localModelURL
             completion:^(NSURL * _Nullable URL, NSError * _Nullable error) {
               [self hideLoadingUI];
               if (error != nil) {
                 // Handle download error
                 [self showErrorUI];
               } else {
                 // Download success, initialize detector
                 [self initializeDetectorWithURL:URL];
               }
             }];
}

- (void)initializeDetectorWithURL:(NSURL *)modelURL {
  MLKLocalModel *localModel = [[MLKLocalModel alloc] initWithPath:modelURL.path];
  MLKCustomObjectDetectorOptions *options = [[MLKCustomObjectDetectorOptions alloc] initWithLocalModel:localModel];
  options.detectorMode = MLKObjectDetectorModeSingleImage;
  options.shouldEnableClassification = YES;
  options.shouldEnableMultipleObjects = YES;
  self.objectDetector = [MLKObjectDetector objectDetectorWithOptions:options];

  // Enable ML features in UI
  [self enableMLFeatures];
}

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

  • Detecção ao vivo e rastreamento do objeto mais proeminente no visor da câmera.
  • A 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 a .orientation correta.

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

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. Receber informações sobre objetos rotulados

Se a chamada para o processador de imagem for bem-sucedida, ela transmitirá uma lista de Object para o gerenciador de conclusão ou retornará a lista, caso você tenha 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 na imagem.
trackingID Um número inteiro que identifica o objeto nas imagens ou "nil" em SINGLE_IMAGE_MODE.
labels
label.text A descrição textual do rótulo. Só será retornado se os metadados do modelo LiteRT contiverem descrições de rótulos.
label.index O índice do rótulo entre todos os rótulos aceitos pelo classificador.
label.confidence O nível de confiança da classificação do objeto.

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

Garantir uma ótima experiência do usuário

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

  • A detecção de objeto bem-sucedida depende da complexidade visual do objeto. Para serem detectados, objetos com poucos recursos visuais podem precisar ocupar uma parte maior da imagem. 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 aceitas, implemente o tratamento especial para objetos desconhecidos.

Além disso, confira o app de demonstração do Kit de ML com Material Design e a coleção de Material Design de Padrões para recursos com tecnologia de machine learning.

Como melhorar o desempenho

Se você quiser usar a detecção de objetos em um aplicativo em tempo real, siga estas diretrizes para conseguir as melhores taxas de frames:

  • Ao usar o modo de streaming em um aplicativo em tempo real, não use a detecção de vários objetos, porque a maioria dos dispositivos não será capaz de produzir taxas de frames adequadas.

  • Para processar frames de vídeo, use a API síncrona results(in:) do detector. Chame esse método da função AVCaptureVideoDataOutputSampleBufferDelegate captureOutput(_, didOutput:from:) para receber resultados de forma síncrona do frame de vídeo especificado. Mantenha o AVCaptureVideoDataOutput alwaysDiscardsLateVideoFrames como true para limitar as chamadas ao detector. Se um novo frame de vídeo ficar disponível durante a execução do detector, 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. 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 quadro de entrada processado. Consulte updatePreviewOverlayViewWithLastFrame na amostra do guia de início rápido do Kit de ML para conferir um exemplo.