iOS でカスタム分類モデルを使用してオブジェクトを検出、追跡、分類する

ML Kit を使用すると、連続する動画フレーム内のオブジェクトを検出してトラックできます。

画像を ML Kit に渡すと、画像内の最大 5 つのオブジェクトと、各オブジェクトの画像内での位置が検出されます。動画ストリーム内のオブジェクトを検出する場合は、すべてのオブジェクトに一意の ID が割り当てられます。この ID を使用して、フレームごとにオブジェクトをトラックできます。

カスタム画像分類モデルを使用して、検出されたオブジェクトを分類できます。モデルの互換性要件、事前トレーニング済みモデルの入手先、 独自のモデルをトレーニングする方法については、ML Kit のカスタムモデルをご覧ください。

カスタムモデルを統合する方法は 2 つあります。モデルをアプリのアセット フォルダに配置してバンドルする方法と、Cloud Storage から動的にダウンロードする方法があります。次の表に、2 つのオプションを比較します。

バンドル モデル ホストされているモデル
モデルはアプリの .ipa ファイルの一部であるため、 ファイルサイズが大きくなります。 モデルはアプリの .ipa ファイルの一部ではありません。Cloud Storage にアップロードすることで ホストされます。Cloud Storage for Firebase を使用することをおすすめします。
このモデルは、Android デバイスがオフラインのときでもすぐに利用可能 アプリには、必要に応じてモデルをダウンロードするコードを含める必要があります
Firebase プロジェクトは不要 Firebase プロジェクトが必要(Cloud Storage for Firebase を使用する場合)。
モデルを更新するにはアプリを再公開する必要があります アプリを再公開することなくモデルの更新を push できる
組み込みの A/B テストはありません Firebase Remote Config を使用した A/B テスト

試してみる

始める前に

  1. Podfile に ML Kit ライブラリを含めます。

    pod 'GoogleMLKit/ObjectDetectionCustom', '8.0.0'
    
  2. プロジェクトの Pod をインストールまたは更新した後に、.xcworkspace を使用して Xcode プロジェクトを開きます。ML Kit は Xcode バージョン 13.2.1 以降でサポートされています。

  3. Cloud Storage for Firebase を使用してモデルをダウンロードする場合は、 まだ行っていない場合は Firebase を iOS プロジェクトに追加します。 これは、モデルをバンドルする場合には必要ありません。

1. モデルを読み込む

ローカル モデルソースを構成する

モデルをアプリにバンドルするには:

  1. モデルファイル(拡張子は通常 .tflite または .lite)を Xcode プロジェクトにコピーします。その際、Copy bundle resources を選択してください。モデルファイルは App Bundle に含められ、ML Kit から使用できます。

  2. LocalModel オブジェクトを作成します。モデルファイルへのパスを指定します。

    Swift

    let localModel = LocalModel(path: localModelFilePath)

    Objective-C

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

リモートでホストされるモデルソースを構成する

リモートでホストされるモデルを使用するには、独自のアプリロジックを使用してモデルファイルをデバイスのローカル ストレージにダウンロードし、ローカルモデルとして読み込む必要があります。モデルをホストするには、Cloud Storage for Firebase を使用することをおすすめします。実装の詳細については、 Firebase ML から Cloud Storage への移行ガイドをご覧ください。

2. オブジェクト検出を構成する

モデルソースを構成したら、CustomObjectDetectorOptions オブジェクトを使用して、ユースケースにオブジェクト検出を構成します。次の設定を変更できます。

オブジェクト検出の設定
検出モード STREAM_MODE (デフォルト) | SINGLE_IMAGE_MODE

STREAM_MODE(デフォルト)では、オブジェクト検出機能は低レイテンシで実行されますが、最初の数回の検出機能の呼び出しで不完全な結果(未指定の境界ボックスやカテゴリラベルなど)が発生する可能性があります。また、STREAM_MODE、 検出機能でオブジェクトにトラッキング ID が割り当てられます。これを使用して、 フレームをまたいでオブジェクトをトラックできます。このモードは、オブジェクトをトラック する場合、または動画ストリームをリアルタイムで処理 する場合のように低レイテンシが重要な場合に使用します。

SINGLE_IMAGE_MODE では、オブジェクトの境界ボックスが決定された後に、オブジェクト検出が結果を返します。分類も有効にすると、境界ボックスとカテゴリラベルの両方が使用可能になった後に結果が返されます。結果として、 検出のレイテンシが潜在的に長くなります。また、 SINGLE_IMAGE_MODE ではトラッキング ID が割り当てられません。レイテンシが重要ではなく、部分的な結果を処理しない場合は、このモードを使用します。

複数のオブジェクトを検出してトラックする false (デフォルト) | true

最大 5 つのオブジェクトを検出してトラックするか、最も 目立つオブジェクトのみをトラックするか(デフォルト)。

オブジェクトを分類する false (デフォルト) | true

提供された カスタム分類モデルを使用して、検出されたオブジェクトを分類するかどうか。カスタム分類 モデルを使用するには、これをtrueに設定する必要があります。

分類の信頼度のしきい値

検出されたラベルの最小信頼スコア。設定しない場合、モデルのメタデータで指定された分類子のしきい値が使用されます。モデルにメタデータが含まれていない場合、またはメタデータで 分類子のしきい値が指定されていない場合は、デフォルトのしきい値 0.0 が 使用されます。

オブジェクトあたりの最大ラベル数

検出器が返すオブジェクトあたりのラベルの最大数。 設定しない場合、デフォルト値の 10 が使用されます。

ローカル バンドルモデルのみがある場合は、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;

リモートでホストされるモデルがある場合は、そのモデルを実行する前にダウンロード済みであることを確認する必要があります。

ダウンロードのステータスの確認はオブジェクト検出器を実行する前に行いますが、リモートでホストされるモデルとローカル バンドル モデルの両方を使用する場合は、ObjectDetector をインスタンス化するときにこの確認を行うという選択肢があります。この方法では、リモートモデルがダウンロードされている場合はリモートモデルから検出器を作成し、リモートモデルがダウンロードされていない場合はローカルモデルから検出器を作成するということが可能です。

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

リモートでホストされるモデルのみがある場合は、モデルがダウンロード済みであることを確認するまで、モデルに関連する機能(UI の一部をグレー表示または非表示にするなど)を無効にする必要があります。

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

オブジェクトの検出とトラッキングの API は主に、次の 2 つのユースケース用に最適化されています。

  • カメラのビューファインダー内で最も目立つオブジェクトをライブで検出してトラッキングする。
  • 静止画像から複数のオブジェクトを検出する。

これらのユースケースに合わせて API を構成するには:

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. 入力画像を準備する

UIImage または CMSampleBuffer を使用して VisionImage オブジェクトを作成します。

UIImage を使用する場合は、次の手順を行います。

  • VisionImage オブジェクトを UIImage で作成します。必ず正しい .orientation を指定してください。

    Swift

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

    Objective-C

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

CMSampleBuffer を使用する場合は、次の手順を行います。

  • に含まれる画像データの向きを指定します。CMSampleBuffer

    画像の向きは次のように取得します。

    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;
      }
    }
          
  • VisionImage オブジェクトと向きを使用して 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. オブジェクト検出器を作成して実行する

  1. 新しいオブジェクト検出器を作成します。

    Swift

    let objectDetector = ObjectDetector.objectDetector(options: options)

    Objective-C

    MLKObjectDetector *objectDetector = [MLKObjectDetector objectDetectorWithOptions:options];
  2. 次に、検出機能を使用します。

    非同期:

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

    同期:

    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. ラベル付きオブジェクトに関する情報を取得する

イメージ プロセッサへの呼び出しが成功した場合は、非同期メソッドと同期メソッドのどちらを呼び出したかに応じて、Object のリストを完了ハンドラに渡すか、リストを返します。

Object には次のプロパティが含まれています。

frame 画像内のオブジェクトの位置を示す CGRect
trackingID イメージ間でオブジェクトを識別する整数。 SINGLE_IMAGE_MODE の場合は `nil`。
labels
label.text ラベルのテキスト説明。LiteRT モデルの メタデータにラベルの説明が含まれている場合にのみ返されます。
label.index 分類子でサポートされているすべてのラベルにおけるラベルのインデックス。
label.confidence オブジェクト分類の信頼値。

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

優れたユーザー エクスペリエンスを確保する

最高のユーザー エクスペリエンスを実現するには、アプリで次のガイドラインに従ってください。

  • オブジェクト検出の成功は、オブジェクトの視覚的な複雑さによります。視覚的特徴の少ないオブジェクトは、検出対象の画像の大部分を占めていないと検出に成功しない可能性があります。検出するオブジェクトの種類に適した入力をキャプチャするためのガイダンスを用意する必要があります。
  • 分類を使用するときに、サポート対象のカテゴリに該当しないオブジェクトを検出する場合は、未知のオブジェクトに対して特別な処理を実装してください。

また、 ML Kit Material Design ショーケース アプリと Material Design の Patterns for machine learning-powered featuresのコレクションも確認してください。

パフォーマンスの向上

リアルタイムのアプリケーションでオブジェクト検出を使用する場合は、適切なフレームレートを得るために次のガイドラインに従ってください。

  • リアルタイム アプリケーションでストリーミング モードを使用する場合は、複数のオブジェクト検出を使用しないでください。ほとんどのデバイスは十分なフレームレートを生成できません。

  • 動画フレームを処理するには、検出機能の results(in:) 同期 API を使用します。このメソッドを AVCaptureVideoDataOutputSampleBufferDelegate captureOutput(_, didOutput:from:) 関数から呼び出して、指定された動画 フレームから結果を同期的に取得します。検出器の呼び出しをスロットルするために、 AVCaptureVideoDataOutput alwaysDiscardsLateVideoFramestrue のままにします。検出器の実行中に新しい 動画フレームが使用可能になると、そのフレームは破棄されます。
  • 検出器の出力を使用して入力画像の上にグラフィックスをオーバーレイする場合は、まず ML Kit から検出結果を取得し、画像とオーバーレイを 1 つのステップでレンダリングします。これにより、ディスプレイ サーフェスへのレンダリングは 処理済みの入力フレームごとに 1 回で済みます。例については、ML Kit クイックスタート サンプルの updatePreviewOverlayViewWithLastFrame をご覧ください。