在 iOS 上使用自訂分類模型偵測、追蹤物件並分類

您可以使用 ML Kit 偵測並追蹤連續影片畫面中的物件。

將圖片傳送至 ML Kit 時,除了圖片中每個物件的位置外,圖片也會偵測出最多五個物件。在偵測影片串流中的物件時,每個物件都有專屬的 ID,可用來追蹤影格之間的物件。

您可以使用自訂圖片分類模型,將偵測到的物件分類。請參閱使用 ML Kit 自訂模型的相關指南,瞭解模型相容性需求、如何尋找預先訓練模型,以及如何訓練模型。

整合自訂模式的方法有兩種。您可以將該模型封裝至應用程式的資產資料夾內,也可以從 Firebase 動態下載模型。下表比較了這兩個選項。

套裝模型 代管模型
模型是應用程式 .ipa 檔案的一部分,因此會增加其大小。 模型不是應用程式 .ipa 檔案的一部分。託管於 Firebase 機器學習
即使 Android 裝置處於離線狀態,也可立即使用模型 隨選下載模型
不需要 Firebase 專案 必須有 Firebase 專案
您必須重新發布應用程式,才能更新模型 不必重新發布應用程式即可推送模型更新
沒有內建 A/B 測試 使用 Firebase 遠端設定輕鬆進行 A/B 版本測試

立即體驗

事前準備

  1. 在 Podfile 中加入 ML Kit 程式庫:

    如何將模型與應用程式組合在一起:

    pod 'GoogleMLKit/ObjectDetectionCustom', '3.2.0'
    

    如要從 Firebase 動態下載模型,請新增 LinkFirebase 依附元件:

    pod 'GoogleMLKit/ObjectDetectionCustom', '3.2.0'
    pod 'GoogleMLKit/LinkFirebase', '3.2.0'
    
  2. 安裝或更新專案的 Pod 後,使用 .xcworkspace 開啟 Xcode 專案。Xcode 13.2.1 以上版本支援機器學習套件。

  3. 如要下載模型,請先將 Firebase 新增至您的 iOS 專案 (如果還沒有的話),那麼建立模型時並不需要這麼做。

1. 載入模型

設定本機模型來源

如何將模型與應用程式整合:

  1. 將模型檔案 (通常以 .tflite.lite 結尾) 複製到您的 Xcode 專案,同時務必選取 Copy bundle resources。模型檔案會包含在應用程式套件中,可供 ML Kit 使用。

  2. 建立 LocalModel 物件,指定模型檔案的路徑:

    Swift

    let localModel = LocalModel(path: localModelFilePath)

    Objective-C

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

設定 Firebase 代管的模型來源

如要使用遠端託管模型,請建立 CustomRemoteModel 物件,並指定您發布模型時指派的名稱:

Swift

let firebaseModelSource = FirebaseModelSource(
    name: "your_remote_model") // The name you assigned in
                               // the Firebase console.
let remoteModel = CustomRemoteModel(remoteModelSource: firebaseModelSource)

Objective-C

MLKFirebaseModelSource *firebaseModelSource =
    [[MLKFirebaseModelSource alloc]
        initWithName:@"your_remote_model"]; // The name you assigned in
                                            // the Firebase console.
MLKCustomRemoteModel *remoteModel =
    [[MLKCustomRemoteModel alloc]
        initWithRemoteModelSource:firebaseModelSource];

然後啟動模型下載工作,指定要允許下載的條件。如果裝置上沒有模型,或有新版模型可用,則工作會以非同步方式從 Firebase 下載模型:

Swift

let downloadConditions = ModelDownloadConditions(
  allowsCellularAccess: true,
  allowsBackgroundDownloading: true
)

let downloadProgress = ModelManager.modelManager().download(
  remoteModel,
  conditions: downloadConditions
)

Objective-C

MLKModelDownloadConditions *downloadConditions =
    [[MLKModelDownloadConditions alloc] initWithAllowsCellularAccess:YES
                                         allowsBackgroundDownloading:YES];

NSProgress *downloadProgress =
    [[MLKModelManager modelManager] downloadModel:remoteModel
                                       conditions:downloadConditions];

許多應用程式均會在其初始化程式碼中啟動下載工作,但您也可以在使用模型前隨時進行這項操作。

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;

如果您有遠端代管的模型,在執行模型之前,必須檢查是否已下載該模型。您可以使用模型管理員的 isModelDownloaded(remoteModel:) 方法,檢查模型下載任務的狀態。

雖然您只需在執行物件偵測工具前確認這一點,但是如果您同時擁有遠端託管模型和本機組合模型,那麼在對 ObjectDetector 執行個體化時,必須進行這項檢查:從遠端模型建立偵測工具 (如果已經下載的話,或從本機模型建立偵測工具)。

Swift

var options: CustomObjectDetectorOptions!
if (ModelManager.modelManager().isModelDownloaded(remoteModel)) {
  options = CustomObjectDetectorOptions(remoteModel: remoteModel)
} else {
  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;
if ([[MLKModelManager modelManager] isModelDownloaded:remoteModel]) {
  options = [[MLKCustomObjectDetectorOptions alloc] initWithRemoteModel:remoteModel];
} else {
  options = [[MLKCustomObjectDetectorOptions alloc] initWithLocalModel:localModel];
}
options.detectorMode = MLKObjectDetectorModeSingleImage;
options.shouldEnableClassification = YES;
options.shouldEnableMultipleObjects = YES;
options.classificationConfidenceThreshold = @(0.5);
options.maxPerObjectLabelCount = 3;

如果您只有遠端託管的模型,在停用模型下載之前,請先停用模型相關功能 (例如顯示為灰色或隱藏 UI 的部分)。

只要將觀察器附加至預設的通知中心,即可取得模型下載狀態。務必在觀察器區塊中使用 self 的弱參照,因為下載可能需要一些時間,而且下載作業可以在下載完成時釋出。例如:

Swift

NotificationCenter.default.addObserver(
    forName: .mlkitModelDownloadDidSucceed,
    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: .mlkitModelDownloadDidFail,
    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]
    // ...
}

Objective-C

__weak typeof(self) weakSelf = self;

[NSNotificationCenter.defaultCenter
    addObserverForName:MLKModelDownloadDidSucceedNotification
                object:nil
                 queue:nil
            usingBlock:^(NSNotification *_Nonnull note) {
              if (weakSelf == nil | note.userInfo == nil) {
                return;
              }
              __strong typeof(self) strongSelf = weakSelf;

              MLKRemoteModel *model = note.userInfo[MLKModelDownloadUserInfoKeyRemoteModel];
              if ([model.name isEqualToString:@"your_remote_model"]) {
                // The model was downloaded and is available on the device
              }
            }];

[NSNotificationCenter.defaultCenter
    addObserverForName:MLKModelDownloadDidFailNotification
                object:nil
                 queue:nil
            usingBlock:^(NSNotification *_Nonnull note) {
              if (weakSelf == nil | note.userInfo == nil) {
                return;
              }
              __strong typeof(self) strongSelf = weakSelf;

              NSError *error = note.userInfo[MLKModelDownloadUserInfoKeyError];
            }];

物件偵測與追蹤 API 已針對以下兩個核心用途進行最佳化處理:

  • 即時偵測及追蹤相機觀景窗中最顯眼的物件。
  • 從靜態圖片偵測多個物件。

如要針對這些用途設定 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. 準備輸入圖片

使用 UIImageCMSampleBuffer 建立 VisionImage 物件。

如果您使用 UIImage,請按照下列步驟操作:

  • 使用 UIImage 建立 VisionImage 物件。請務必指定正確的 .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;
      }
    }
          
  • 使用 CMSampleBuffer 物件和方向建立 VisionImage 物件:

    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 標籤的文字說明。僅當 TensorFlow Lite 模型的中繼資料包含標籤說明時才會傳回。
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 展示應用程式][showcase-link]{: .external } 和質感設計採用機器學習技術的模式集合。

提升效能

如果您想在即時應用程式中使用物件偵測,請遵守下列指南,以達到最佳影格速率:

  • 在即時應用程式中使用串流模式時,請勿使用多個物件偵測,因為大部分裝置無法產生適當的畫面速率。

  • 如要處理影格,請使用偵測工具的 results(in:) 同步 API。從 AVCaptureVideoDataOutputSampleBufferDelegatecaptureOutput(_, didOutput:from:) 函式呼叫此方法,即可同步取得特定影片影格的結果。將 AVCaptureVideoDataOutputalwaysDiscardsLateVideoFrames 保留為 true,以限制對偵測工具的呼叫。假如在偵測器執行期間有新的視訊畫面可用,系統就會捨棄該影格。
  • 如果您使用偵測工具的輸出內容,為輸入圖片上的圖像重疊,請先透過 ML Kit 取得結果,然後透過單一步驟算繪圖像和疊加層。如此一來,每個處理的輸入影格只會轉譯一次到顯示途徑一次。如需範例,請參閱 ML Kit 快速入門導覽課程範例中的 updatePreviewOverlayViewWithLastFrame