Firebase Auth と Functions を使用して Cloud Vision でランドマークを安全に認識する(Apple プラットフォーム)

アプリから Google Cloud API を呼び出すには、認可を処理し、API キーなどのシークレット値を保護するための中間 REST API を作成する必要があります。次に、モバイルアプリでこの中間サービスに対する認証と通信を行うためのコードを記述します。

この REST API を作成する方法の一つとして、Firebase Authentication と Functions を使用する方法があります。この方法では、Google Cloud API に対するサーバーレスのマネージド ゲートウェイが提供され、そこで認証が処理されます。このゲートウェイは、事前構築された SDK を使用してモバイルアプリから呼び出すことができます。

このガイドでは、この手法を使用してアプリから Cloud Vision API を呼び出す方法について説明します。この手法では、すべての認証済みユーザーが Cloud のプロジェクトを経由して Cloud Vision の課金サービスにアクセスできます。そのため、続行する前に、目的のユースケースにおいてこの認証メカニズムで十分かどうかを検討してください。

始める前に

プロジェクトを構成する

まだアプリに Firebase を追加していない場合は、スタートガイドの手順に沿って追加してください。

Swift Package Manager を使用して Firebase の依存関係をインストールし、管理します。

  1. Xcode でアプリのプロジェクトを開いたまま、[File] > [Add Packages] の順に移動します。
  2. プロンプトが表示されたら、Firebase Apple プラットフォーム SDK リポジトリを追加します。
  3.   https://github.com/firebase/firebase-ios-sdk.git
  4. Firebase ML ライブラリを選択します。
  5. ターゲットのビルド設定の [Other Linker Flags] セクションに -ObjC フラグを追加します。
  6. 上記の作業が完了すると、Xcode は依存関係の解決とバックグラウンドでのダウンロードを自動的に開始します。

次に、アプリ内の設定を行います。

  1. アプリに Firebase をインポートします。

    Swift

    import FirebaseMLModelDownloader

    Objective-C

    @import FirebaseMLModelDownloader;

あと数ステップの構成作業を行うと、準備が完了します。

  1. プロジェクトで Cloud ベースの API をまだ有効にしていない場合は、ここで有効にします。

    1. Firebase コンソールの Firebase ML の [APIs] ページを開きます。
    2. まだプロジェクトを Blaze 料金プランにアップグレードしていない場合は、[アップグレード] をクリックしてアップグレードします(プロジェクトをアップグレードするよう求められるのは、プロジェクトが Blaze プランでない場合のみです)。

      Blaze レベルのプロジェクトだけが Cloud ベースの API を使用できます。

    3. Cloud ベースの API がまだ有効になっていない場合は、[Cloud ベースの API を有効化] をクリックします。
  2. 既存の Firebase API キーを構成して、Cloud Vision API へのアクセスを許可しないようにします。
    1. Cloud コンソールの [認証情報] ページを開きます。
    2. リスト内の各 API キーについて、編集ビューを開き、[キーの制限] セクションで Cloud Vision API を除く使用可能なすべての API をリストに追加します。

呼び出し可能関数をデプロイする

次に、アプリと Cloud Vision API の間のブリッジとして使用する Cloud Functions の関数をデプロイします。functions-samples リポジトリには、使用可能なサンプルが含まれています。

デフォルトでは、この関数を通じて Cloud Vision API にアクセスすると、アプリの認証済みユーザーのみが Cloud Vision API にアクセスできます。関数はさまざまな要件に応じて変更できます。

関数をデプロイするには:

  1. 関数サンプル リポジトリをダウンロードするか、そのクローンを作成して、Node-1st-gen/vision-annotate-image ディレクトリに移動します。
    git clone https://github.com/firebase/functions-samples
    cd Node-1st-gen/vision-annotate-image
    
  2. 依存関係をインストールします。
    cd functions
    npm install
    cd ..
  3. Firebase CLI がインストールされていない場合は、インストールします。
  4. vision-annotate-image ディレクトリ内で Firebase プロジェクトを初期化します。プロンプトが表示されたら、リストからプロジェクトを選択します。
    firebase init
  5. 関数をデプロイします。
    firebase deploy --only functions:annotateImage

アプリに Firebase Auth を追加する

上記でデプロイした呼び出し可能関数は、アプリ内で認証されていないユーザーからのリクエストを拒否します。アプリにまだ Firebase Auth を追加していない場合は、Firebase Auth を追加する必要があります。

必要な依存関係をアプリに追加する

Swift Package Manager を使用して Cloud Functions for Firebase ライブラリをインストールします。

1. 入力画像を準備する

Cloud Vision を呼び出すには、画像を base64 でエンコードされた文字列としてフォーマットする必要があります。以下の方法で UIImage を処理します。

Swift

guard let imageData = uiImage.jpegData(compressionQuality: 1.0) else { return }
let base64encodedImage = imageData.base64EncodedString()

Objective-C

NSData *imageData = UIImageJPEGRepresentation(uiImage, 1.0f);
NSString *base64encodedImage =
  [imageData base64EncodedStringWithOptions:NSDataBase64Encoding76CharacterLineLength];

2. 呼び出し可能関数を呼び出し、ランドマークを認識する

画像内のランドマークを認識するには、呼び出し可能関数を呼び出し、JSON Cloud Vision リクエストを渡します。

  1. 最初に Cloud Functions のインスタンスを初期化します。

    Swift

    lazy var functions = Functions.functions()
    

    Objective-C

    @property(strong, nonatomic) FIRFunctions *functions;
    
  2. リクエストを作成し、タイプとして LANDMARK_DETECTION を設定します。

    Swift

    let requestData = [
      "image": ["content": base64encodedImage],
      "features": ["maxResults": 5, "type": "LANDMARK_DETECTION"]
    ]
    

    Objective-C

    NSDictionary *requestData = @{
      @"image": @{@"content": base64encodedImage},
      @"features": @{@"maxResults": @5, @"type": @"LANDMARK_DETECTION"}
    };
    
  3. 関数を呼び出します。

    Swift

    do {
      let result = try await functions.httpsCallable("annotateImage").call(requestData)
      print(result)
    } catch {
      if let error = error as NSError? {
        if error.domain == FunctionsErrorDomain {
          let code = FunctionsErrorCode(rawValue: error.code)
          let message = error.localizedDescription
          let details = error.userInfo[FunctionsErrorDetailsKey]
        }
        // ...
      }
    }
    

    Objective-C

    [[_functions HTTPSCallableWithName:@"annotateImage"]
                              callWithObject:requestData
                                  completion:^(FIRHTTPSCallableResult * _Nullable result, NSError * _Nullable error) {
            if (error) {
              if ([error.domain isEqualToString:@"com.firebase.functions"]) {
                FIRFunctionsErrorCode code = error.code;
                NSString *message = error.localizedDescription;
                NSObject *details = error.userInfo[@"details"];
              }
              // ...
            }
            // Function completed succesfully
            // Get information about labeled objects
    
          }];
    

3. 認識されたランドマークに関する情報を取得する

ランドマーク認識処理が成功すると、その結果として BatchAnnotateImagesResponse の JSON レスポンスが返されます。landmarkAnnotations 配列内の各オブジェクトは、画像内で認識されたランドマークを表します。ランドマークごとに、入力画像の境界座標、ランドマーク名、緯度と経度、ナレッジグラフ エンティティ ID(使用できる場合)、一致の信頼スコアを取得できます。次に例を示します。

Swift

if let labelArray = (result?.data as? [String: Any])?["landmarkAnnotations"] as? [[String:Any]] {
  for labelObj in labelArray {
    let landmarkName = labelObj["description"]
    let entityId = labelObj["mid"]
    let score = labelObj["score"]
    let bounds = labelObj["boundingPoly"]
    // Multiple locations are possible, e.g., the location of the depicted
    // landmark and the location the picture was taken.
    guard let locations = labelObj["locations"] as? [[String: [String: Any]]] else { continue }
    for location in locations {
      let latitude = location["latLng"]?["latitude"]
      let longitude = location["latLng"]?["longitude"]
    }
  }
}

Objective-C

NSArray *labelArray = result.data[@"landmarkAnnotations"];
for (NSDictionary *labelObj in labelArray) {
  NSString *landmarkName = labelObj[@"description"];
  NSString *entityId = labelObj[@"mid"];
  NSNumber *score = labelObj[@"score"];
  NSArray *bounds = labelObj[@"boundingPoly"];
  // Multiple locations are possible, e.g., the location of the depicted
  // landmark and the location the picture was taken.
  NSArray *locations = labelObj[@"locations"];
  for (NSDictionary *location in locations) {
    NSNumber *latitude = location[@"latLng"][@"latitude"];
    NSNumber *longitude = location[@"latLng"][@"longitude"];
  }
}