获取我们在 Firebase 峰会上发布的所有信息,了解 Firebase 可如何帮助您加快应用开发速度并满怀信心地运行应用。了解详情

Detecte objetos en imágenes con un modelo entrenado con AutoML en plataformas Apple

Después de entrenar su propio modelo con AutoML Vision Edge , puede usarlo en su aplicación para detectar objetos en las imágenes.

Hay dos formas de integrar modelos entrenados desde AutoML Vision Edge. Puede agrupar el modelo copiando los archivos del modelo en su proyecto Xcode, o puede descargarlo dinámicamente desde Firebase.

Opciones de agrupación de modelos
Incluido en su aplicación
  • El modelo es parte del paquete.
  • El modelo está disponible de inmediato, incluso cuando el dispositivo Apple está fuera de línea
  • No es necesario un proyecto de Firebase
Alojado con Firebase
  • Aloje el modelo cargándolo en Firebase Machine Learning
  • Reduce el tamaño del paquete de aplicaciones
  • El modelo se descarga bajo demanda.
  • Envía actualizaciones de modelos sin volver a publicar tu aplicación
  • Pruebas A/B sencillas con Firebase Remote Config
  • Requiere un proyecto de Firebase

Antes de que empieces

  1. Si desea descargar un modelo , asegúrese de agregar Firebase a su proyecto de Apple , si aún no lo ha hecho. Esto no es necesario cuando empaqueta el modelo.

  2. Incluya las bibliotecas TensorFlow y Firebase en su Podfile:

    Para agrupar un modelo con su aplicación:

    Rápido

    pod 'TensorFlowLiteSwift'
    

    C objetivo

    pod 'TensorFlowLiteObjC'
    

    Para descargar dinámicamente un modelo de Firebase, agregue la dependencia de Firebase/MLModelInterpreter :

    Rápido

    pod 'TensorFlowLiteSwift'
    pod 'Firebase/MLModelInterpreter'
    

    C objetivo

    pod 'TensorFlowLiteObjC'
    pod 'Firebase/MLModelInterpreter'
    
  3. Después de instalar o actualizar los Pods de su proyecto, abra su proyecto Xcode usando su .xcworkspace .

1. Cargue el modelo

Configurar una fuente de modelo local

Para agrupar el modelo con su aplicación, copie el modelo y el archivo de etiquetas en su proyecto de Xcode, teniendo cuidado de seleccionar Crear referencias de carpeta cuando lo haga. El archivo del modelo y las etiquetas se incluirán en el paquete de la aplicación.

Además, observe el archivo tflite_metadata.json que se creó junto con el modelo. Necesitas dos valores:

  • Las dimensiones de entrada del modelo. Esto es 320x320 por defecto.
  • Las detecciones máximas del modelo. Esto es 40 por defecto.

Configurar una fuente de modelo alojada en Firebase

Para usar el modelo alojado de forma remota, cree un objeto CustomRemoteModel , especificando el nombre que le asignó al modelo cuando lo publicó:

Rápido

let remoteModel = CustomRemoteModel(
    name: "your_remote_model"  // The name you assigned in the Google Cloud Console.
)

C objetivo

FIRCustomRemoteModel *remoteModel = [[FIRCustomRemoteModel alloc]
                                     initWithName:@"your_remote_model"];

Luego, inicie la tarea de descarga del modelo, especificando las condiciones bajo las cuales desea permitir la descarga. Si el modelo no está en el dispositivo, o si hay disponible una versión más reciente del modelo, la tarea descargará el modelo de forma asíncrona desde Firebase:

Rápido

let downloadProgress = ModelManager.modelManager().download(
    remoteModel,
    conditions: ModelDownloadConditions(
        allowsCellularAccess: true,
        allowsBackgroundDownloading: true
    )
)

C objetivo

FIRModelDownloadConditions *conditions =
        [[FIRModelDownloadConditions alloc] initWithAllowsCellularAccess:YES
                                             allowsBackgroundDownloading:YES];
NSProgress *progress = [[FIRModelManager modelManager] downloadModel:remoteModel
                                                          conditions:conditions];

Muchas aplicaciones inician la tarea de descarga en su código de inicialización, pero puede hacerlo en cualquier momento antes de que necesite usar el modelo.

Cree un detector de objetos a partir de su modelo

Después de configurar las fuentes de su modelo, cree un objeto TensorFlow Lite Interpreter a partir de una de ellas.

Si solo tiene un modelo empaquetado localmente, simplemente cree un intérprete a partir del archivo del modelo:

Rápido

guard let modelPath = Bundle.main.path(
    forResource: "model",
    ofType: "tflite"
) else {
  print("Failed to load the model file.")
  return true
}
let interpreter = try Interpreter(modelPath: modelPath)
try interpreter.allocateTensors()

C objetivo

NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"model"
                                                      ofType:@"tflite"];

NSError *error;
TFLInterpreter *interpreter = [[TFLInterpreter alloc] initWithModelPath:modelPath
                                                                  error:&error];
if (error != NULL) { return; }

[interpreter allocateTensorsWithError:&error];
if (error != NULL) { return; }

Si tiene un modelo alojado de forma remota, deberá verificar que se haya descargado antes de ejecutarlo. Puede comprobar el estado de la tarea de descarga del modelo mediante el método isModelDownloaded(remoteModel:) del administrador de modelos.

Aunque solo tiene que confirmar esto antes de ejecutar el intérprete, si tiene un modelo alojado de forma remota y un modelo empaquetado localmente, podría tener sentido realizar esta verificación al instanciar el Interpreter : cree un intérprete desde el modelo remoto si es sido descargado, y del modelo local en caso contrario.

Rápido

var modelPath: String?
if ModelManager.modelManager().isModelDownloaded(remoteModel) {
    ModelManager.modelManager().getLatestModelFilePath(remoteModel) { path, error in
        guard error == nil else { return }
        guard let path = path else { return }
        modelPath = path
    }
} else {
    modelPath = Bundle.main.path(
        forResource: "model",
        ofType: "tflite"
    )
}

guard modelPath != nil else { return }
let interpreter = try Interpreter(modelPath: modelPath)
try interpreter.allocateTensors()

C objetivo

__block NSString *modelPath;
if ([[FIRModelManager modelManager] isModelDownloaded:remoteModel]) {
    [[FIRModelManager modelManager] getLatestModelFilePath:remoteModel
                                                completion:^(NSString * _Nullable filePath,
                                                             NSError * _Nullable error) {
        if (error != NULL) { return; }
        if (filePath == NULL) { return; }
        modelPath = filePath;
    }];
} else {
    modelPath = [[NSBundle mainBundle] pathForResource:@"model"
                                                ofType:@"tflite"];
}

NSError *error;
TFLInterpreter *interpreter = [[TFLInterpreter alloc] initWithModelPath:modelPath
                                                                  error:&error];
if (error != NULL) { return; }

[interpreter allocateTensorsWithError:&error];
if (error != NULL) { return; }

Si solo tiene un modelo alojado de forma remota, debe deshabilitar la funcionalidad relacionada con el modelo, por ejemplo, atenuar u ocultar parte de su interfaz de usuario, hasta que confirme que el modelo se ha descargado.

Puede obtener el estado de descarga del modelo adjuntando observadores al Centro de notificaciones predeterminado. Asegúrese de usar una referencia débil a self en el bloque del observador, ya que las descargas pueden llevar algún tiempo y el objeto de origen puede liberarse cuando finaliza la descarga. Por ejemplo:

Rápido

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]
    // ...
}

C objetivo

__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

A continuación, debe preparar sus imágenes para el intérprete de TensorFlow Lite.

  1. Recorte y escale la imagen a las dimensiones de entrada del modelo, como se especifica en el archivo tflite_metadata.json (320x320 píxeles de forma predeterminada). Puede hacer esto con Core Image o una biblioteca de terceros

  2. Copie los datos de la imagen en un Data (objeto NSData ):

    Rápido

    guard let image: CGImage = // Your input image
    guard let context = CGContext(
      data: nil,
      width: image.width, height: image.height,
      bitsPerComponent: 8, bytesPerRow: image.width * 4,
      space: CGColorSpaceCreateDeviceRGB(),
      bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue
    ) else {
      return nil
    }
    
    context.draw(image, in: CGRect(x: 0, y: 0, width: image.width, height: image.height))
    guard let imageData = context.data else { return nil }
    
    var inputData = Data()
    for row in 0 ..< 320 {    // Model takes 320x320 pixel images as input
      for col in 0 ..< 320 {
        let offset = 4 * (col * context.width + row)
        // (Ignore offset 0, the unused alpha channel)
        var red = imageData.load(fromByteOffset: offset+1, as: UInt8.self)
        var green = imageData.load(fromByteOffset: offset+2, as: UInt8.self)
        var blue = imageData.load(fromByteOffset: offset+3, as: UInt8.self)
    
        inputData.append(&red, count: 1)
        inputData.append(&green, count: 1)
        inputData.append(&blue, count: 1)
      }
    }
    

    C objetivo

    CGImageRef image = // Your input image
    long imageWidth = CGImageGetWidth(image);
    long imageHeight = CGImageGetHeight(image);
    CGContextRef context = CGBitmapContextCreate(nil,
                                                 imageWidth, imageHeight,
                                                 8,
                                                 imageWidth * 4,
                                                 CGColorSpaceCreateDeviceRGB(),
                                                 kCGImageAlphaNoneSkipFirst);
    CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), image);
    UInt8 *imageData = CGBitmapContextGetData(context);
    
    NSMutableData *inputData = [[NSMutableData alloc] initWithCapacity:0];
    
    for (int row = 0; row < 300; row++) {
      for (int col = 0; col < 300; col++) {
        long offset = 4 * (row * imageWidth + col);
        // (Ignore offset 0, the unused alpha channel)
        UInt8 red = imageData[offset+1];
        UInt8 green = imageData[offset+2];
        UInt8 blue = imageData[offset+3];
    
        [inputData appendBytes:&red length:1];
        [inputData appendBytes:&green length:1];
        [inputData appendBytes:&blue length:1];
      }
    }
    

3. Ejecute el detector de objetos

A continuación, pase la entrada preparada al intérprete:

Rápido

try interpreter.copy(inputData, toInputAt: 0)
try interpreter.invoke()

C objetivo

TFLTensor *input = [interpreter inputTensorAtIndex:0 error:&error];
if (error != nil) { return; }

[input copyData:inputData error:&error];
if (error != nil) { return; }

[interpreter invokeWithError:&error];
if (error != nil) { return; }

4. Obtenga información sobre los objetos detectados

Si la detección de objetos tiene éxito, el modelo produce como salida tres matrices de 40 elementos (o lo que se haya especificado en el archivo tflite_metadata.json ) cada una. Cada elemento corresponde a un objeto potencial. La primera matriz es una matriz de cuadros delimitadores; el segundo, una matriz de etiquetas; y el tercero, una matriz de valores de confianza. Para obtener los resultados del modelo:

Rápido

var output = try interpreter.output(at: 0)
let boundingBoxes =
    UnsafeMutableBufferPointer<Float32>.allocate(capacity: 4 * 40)
output.data.copyBytes(to: boundingBoxes)

output = try interpreter.output(at: 1)
let labels =
    UnsafeMutableBufferPointer<Float32>.allocate(capacity: 40)
output.data.copyBytes(to: labels)

output = try interpreter.output(at: 2)
let probabilities =
    UnsafeMutableBufferPointer<Float32>.allocate(capacity: 40)
output.data.copyBytes(to: probabilities)

C objetivo

TFLTensor *output = [interpreter outputTensorAtIndex:0 error:&error];
if (error != nil) { return; }
NSData *boundingBoxes = [output dataWithError:&error];
if (error != nil) { return; }

output = [interpreter outputTensorAtIndex:1 error:&error];
if (error != nil) { return; }
NSData *labels = [output dataWithError:&error];
if (error != nil) { return; }

output = [interpreter outputTensorAtIndex:2 error:&error];
if (error != nil) { return; }
NSData *probabilities = [output dataWithError:&error];
if (error != nil) { return; }

Luego, puede combinar las salidas de etiquetas con su diccionario de etiquetas:

Rápido

guard let labelPath = Bundle.main.path(
    forResource: "dict",
    ofType: "txt"
) else { return true }
let fileContents = try? String(contentsOfFile: labelPath)
guard let labelText = fileContents?.components(separatedBy: "\n") else { return true }

for i in 0 ..< 40 {
    let top = boundingBoxes[0 * i]
    let left = boundingBoxes[1 * i]
    let bottom = boundingBoxes[2 * i]
    let right = boundingBoxes[3 * i]

    let labelIdx = Int(labels[i])
    let label = labelText[labelIdx]
    let confidence = probabilities[i]

    if confidence > 0.66 {
        print("Object found: \(label) (confidence: \(confidence))")
        print("  Top-left: (\(left),\(top))")
        print("  Bottom-right: (\(right),\(bottom))")
    }
}

C objetivo

NSString *labelPath = [NSBundle.mainBundle pathForResource:@"dict"
                                                    ofType:@"txt"];
NSString *fileContents = [NSString stringWithContentsOfFile:labelPath
                                                   encoding:NSUTF8StringEncoding
                                                      error:&error];
if (error != nil || fileContents == NULL) { return; }
NSArray<NSString*> *labelText = [fileContents componentsSeparatedByString:@"\n"];

for (int i = 0; i < 40; i++) {
    Float32 top, right, bottom, left;
    Float32 labelIdx;
    Float32 confidence;

    [boundingBoxes getBytes:&top range:NSMakeRange(16 * i + 0, 4)];
    [boundingBoxes getBytes:&left range:NSMakeRange(16 * i + 4, 4)];
    [boundingBoxes getBytes:&bottom range:NSMakeRange(16 * i + 8, 4)];
    [boundingBoxes getBytes:&right range:NSMakeRange(16 * i + 12, 4)];

    [labels getBytes:&labelIdx range:NSMakeRange(4 * i, 4)];
    [probabilities getBytes:&confidence range:NSMakeRange(4 * i, 4)];

    if (confidence > 0.5f) {
        NSString *label = labelText[(int)labelIdx];
        NSLog(@"Object detected: %@", label);
        NSLog(@"  Confidence: %f", confidence);
        NSLog(@"  Top-left: (%f,%f)", left, top);
        NSLog(@"  Bottom-right: (%f,%f)", right, bottom);
    }
}

Sugerencias para mejorar el rendimiento en tiempo real

Si desea etiquetar imágenes en una aplicación en tiempo real, siga estas pautas para lograr las mejores tasas de cuadros:

  • Llamadas del acelerador al detector. Si un nuevo cuadro de video está disponible mientras el detector está funcionando, suelte el cuadro.
  • Si está utilizando la salida del detector para superponer gráficos en la imagen de entrada, primero obtenga el resultado, luego renderice la imagen y superponga en un solo paso. Al hacerlo, renderiza en la superficie de visualización solo una vez para cada cuadro de entrada. Consulte las clases previewOverlayView y FIRDetectionOverlayView en la aplicación de ejemplo de presentación para ver un ejemplo.