Помечайте изображения с помощью пользовательской модели на iOS

С помощью ML Kit можно распознавать объекты на изображении и присваивать им метки. Этот API поддерживает широкий спектр пользовательских моделей классификации изображений. Для получения информации о требованиях к совместимости моделей, где найти предварительно обученные модели и как обучить собственные модели, обратитесь к разделу «Пользовательские модели с ML Kit» .

Существует два способа интеграции пользовательской модели. Вы можете включить модель в состав приложения, поместив её в папку ресурсов, или динамически загрузить её из облачного хранилища. В следующей таблице сравниваются эти два варианта.

Комплексная модель Хостинговая модель
Эта модель является частью APK-файла вашего приложения, что увеличивает его размер. Эта модель не является частью вашего APK-файла. Она размещается путем загрузки в Cloud Storage. Мы рекомендуем использовать Cloud Storage для Firebase .
Данная модель доступна сразу же, даже когда устройство Android находится в автономном режиме. Ваше приложение должно содержать код для загрузки модели по запросу.
Нет необходимости в проекте Firebase. Требуется проект Firebase (если используется Cloud Storage for Firebase).
Для обновления модели необходимо повторно опубликовать приложение. Обновляйте модель приложения, не переиздавая его.
Встроенного A/B-тестирования нет. A/B-тестирование с использованием Firebase Remote Config

Попробуйте!

Прежде чем начать

  1. Включите библиотеки ML Kit в свой Podfile:

    pod 'GoogleMLKit/ImageLabelingCustom', '8.0.0'
    
  2. После установки или обновления Pods вашего проекта откройте проект Xcode, используя его .xcworkspace . ML Kit поддерживается в Xcode версии 13.2.1 или выше.

  3. Если вы хотите загрузить модель с помощью Cloud Storage for Firebase , убедитесь, что вы добавили Firebase в свой iOS-проект , если вы еще этого не сделали. Это не требуется при сборке модели.

1. Загрузите модель.

Настройте локальный источник модели.

Чтобы включить модель в ваше приложение:

  1. Скопируйте файл модели (обычно с расширением .tflite или .lite ) в свой проект Xcode, обязательно выбрав опцию Copy bundle resources . Файл модели будет включен в пакет приложения и станет доступен для ML Kit.

  2. Создайте объект LocalModel , указав путь к файлу модели:

    Быстрый

    let localModel = LocalModel(path: localModelFilePath)

    Objective-C

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

Настройте удаленно размещенный источник модели.

Для использования модели, размещенной удаленно, необходимо загрузить файл модели в локальное хранилище устройства, используя собственную логику приложения, а затем загрузить его как локальную модель. Мы рекомендуем использовать Cloud Storage for Firebase для размещения модели. Подробности реализации см. в руководстве по миграции Firebase ML в Cloud Storage .

Настройте средство разметки изображений.

После настройки источников модели создайте объект ImageLabeler на основе одного из них.

Доступны следующие варианты:

Параметры
confidenceThreshold

Минимальный показатель достоверности обнаруженных меток. Если не задано, будет использоваться любой пороговый уровень классификатора, указанный в метаданных модели. Если модель не содержит метаданных или метаданные не указывают пороговый уровень классификатора, будет использоваться пороговое значение по умолчанию, равное 0,0.

maxResultCount

Максимальное количество возвращаемых меток. Если не указано, будет использоваться значение по умолчанию — 10.

Если у вас есть только локально упакованная модель, просто создайте объект Labeler на основе объекта LocalModel :

Быстрый

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

Если вы используете удаленно размещенную модель, вам необходимо убедиться, что она была загружена, прежде чем запускать ее.

Хотя подтверждение этого требуется только перед запуском разметчика, если у вас есть как удаленно размещенная модель, так и локально упакованная модель, имеет смысл выполнить эту проверку при создании экземпляра ImageLabeler : создать разметчик из удаленной модели, если она была загружена, и из локальной модели в противном случае.

Быстрый

// 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 = CustomImageLabelerOptions(localModel: model)
let imageLabeler = ImageLabeler.imageLabeler(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];
}

MLKCustomImageLabelerOptions *options = [[MLKCustomImageLabelerOptions alloc] initWithLocalModel:model];
MLKImageLabeler *imageLabeler = [MLKImageLabeler imageLabelerWithOptions:options];

Если у вас есть только удаленно размещенная модель, следует отключить связанные с ней функции — например, сделать часть пользовательского интерфейса неактивной или скрытой — до тех пор, пока вы не убедитесь, что модель загружена.

Быстрый

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.initializeLabeler(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 labeler
      self.initializeLabeler(with: modelURL)
    }
  }
}

func initializeLabeler(with modelURL: URL) {
  let localModel = LocalModel(path: modelURL.path)
  let options = CustomImageLabelerOptions(localModel: localModel)
  self.imageLabeler = ImageLabeler.imageLabeler(options: options)
  // Enable ML-related UI features here
  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 initializeLabelerWithURL: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 labeler
                 [self initializeLabelerWithURL:URL];
               }
             }];
}

- (void)initializeLabelerWithURL:(NSURL *)modelURL {
  MLKLocalModel *localModel = [[MLKLocalModel alloc] initWithPath:modelURL.path];
  MLKCustomImageLabelerOptions *options = [[MLKCustomImageLabelerOptions alloc] initWithLocalModel:localModel];
  self.imageLabeler = [MLKImageLabeler imageLabelerWithOptions:options];

  // Enable ML-related UI features here
  [self enableMLFeatures];
}

2. Подготовьте входное изображение.

Создайте объект VisionImage , используя UIImage или CMSampleBuffer .

Если вы используете UIImage , выполните следующие действия:

  • Создайте объект VisionImage с использованием UIImage . Убедитесь, что указана правильная .orientation ).

    Быстрый

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

    Objective-C

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

Если вы используете CMSampleBuffer , выполните следующие действия:

  • Укажите ориентацию данных изображения, содержащихся в CMSampleBuffer .

    Чтобы определить ориентацию изображения:

    Быстрый

    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;
      }
    }
          
  • Создайте объект VisionImage , используя объект CMSampleBuffer и заданную ориентацию:

    Быстрый

    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. Запустите программу для разметки изображений.

Для добавления меток к объектам на изображении передайте объект image в метод process() класса ImageLabeler .

Асинхронно:

Быстрый

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

Синхронно:

Быстрый

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. Получите информацию о помеченных объектах.

Если операция разметки изображения прошла успешно, она возвращает массив объектов ImageLabel . Каждый ImageLabel представляет собой то, что было помечено на изображении. Вы можете получить текстовое описание каждой метки (если оно доступно в метаданных файла модели LiteRT), оценку достоверности и индекс. Например:

Быстрый

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

Советы по повышению производительности в режиме реального времени

Если вы хотите маркировать изображения в приложении реального времени, следуйте этим рекомендациям, чтобы добиться наилучшей частоты кадров:

  • Для обработки видеокадров используйте синхронный API results(in:) детектора. Вызовите этот метод из функции captureOutput(_, didOutput:from:) в AVCaptureVideoDataOutputSampleBufferDelegate , чтобы синхронно получить результаты из заданного видеокадра. Установите параметр alwaysDiscardsLateVideoFrames в AVCaptureVideoDataOutput в значение true , чтобы ограничить количество вызовов детектора. Если во время работы детектора появится новый видеокадр, он будет отброшен.
  • Если вы используете выходные данные детектора для наложения графики на входное изображение, сначала получите результат от ML Kit, а затем отрендерите изображение и наложение за один шаг. Таким образом, вы будете отрендеривать изображение на поверхности дисплея только один раз для каждого обработанного входного кадра. Пример можно увидеть в функции updatePreviewOverlayViewWithLastFrame в примере быстрого запуска ML Kit.