Firebase Summit에서 발표된 모든 내용을 살펴보고 Firebase로 앱을 빠르게 개발하고 안심하고 앱을 실행하는 방법을 알아보세요. 자세히 알아보기

Android에서 Firebase 인증 및 함수를 사용하여 Cloud Vision으로 이미지에 안전하게 레이블 지정

컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요.

앱에서 Google Cloud API를 호출하려면 승인을 처리하고 API 키와 같은 비밀 값을 보호하는 중간 REST API를 만들어야 합니다. 그런 다음 이 중간 서비스에 인증하고 통신하기 위해 모바일 앱에 코드를 작성해야 합니다.

이 REST API를 만드는 한 가지 방법은 인증을 처리하고 사전 빌드된 SDK로 모바일 앱에서 호출할 수 있는 Google Cloud API에 대한 관리형 서버리스 게이트웨이를 제공하는 Firebase 인증 및 함수를 사용하는 것입니다.

이 가이드에서는 이 기술을 사용하여 앱에서 Cloud Vision API를 호출하는 방법을 보여줍니다. 이 방법을 사용하면 인증된 모든 사용자가 클라우드 프로젝트를 통해 Cloud Vision 청구 서비스에 액세스할 수 있으므로 계속하기 전에 이 인증 메커니즘이 사용 사례에 충분한지 고려하십시오.

시작하기 전에

프로젝트 구성

  1. 아직 Android 프로젝트에 Firebase를 추가 하지 않았다면 추가합니다.
  2. 프로젝트에 클라우드 기반 API를 아직 활성화하지 않았다면 지금 활성화하십시오.

    1. Firebase 콘솔의 Firebase ML API 페이지 를 엽니다.
    2. 아직 프로젝트를 Blaze 요금제로 업그레이드하지 않은 경우 업그레이드 를 클릭하여 업그레이드하십시오. (프로젝트가 Blaze 요금제에 없는 경우에만 업그레이드하라는 메시지가 표시됩니다.)

      Blaze 수준 프로젝트만 클라우드 기반 API를 사용할 수 있습니다.

    3. 클라우드 기반 API가 아직 활성화되지 않은 경우 클라우드 기반 API 활성화 를 클릭합니다.
  3. Cloud Vision API에 대한 액세스를 허용하지 않도록 기존 Firebase API 키를 구성합니다.
    1. Cloud 콘솔의 사용자 인증 정보 페이지를 엽니다.
    2. 목록의 각 API 키에 대해 편집 보기를 열고 키 제한 섹션에서 Cloud Vision API를 제외한 사용 가능한 모든 API를 목록에 추가합니다.

호출 가능한 함수 배포

그런 다음 앱과 Cloud Vision API를 연결하는 데 사용할 Cloud Function을 배포합니다. functions-samples 저장소에는 사용할 수 있는 예제가 포함되어 있습니다.

기본적으로 이 기능을 통해 Cloud Vision API에 액세스하면 앱의 인증된 사용자만 Cloud Vision API에 액세스할 수 있습니다. 다양한 요구 사항에 맞게 기능을 수정할 수 있습니다.

함수를 배포하려면:

  1. functions-samples 리포지토리 를 복제하거나 다운로드하고 vision-annotate-image 디렉토리로 변경합니다.
    git clone https://github.com/firebase/functions-samples
    cd 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 인증 추가

위에 배포된 호출 가능 함수는 인증되지 않은 앱 사용자의 요청을 거부합니다. 아직 추가하지 않았다면 앱에 Firebase 인증을 추가해야 합니다.

앱에 필요한 종속성 추가

  • Firebase Functions 및 gson Android 라이브러리에 대한 종속성을 모듈(앱 수준) Gradle 파일(일반적으로 app/build.gradle)에 추가합니다.
    implementation 'com.google.firebase:firebase-functions:20.2.1'
    implementation 'com.google.code.gson:gson:2.8.6'
    
  • 이제 이미지에 레이블을 지정할 준비가 되었습니다.

    1. 입력 이미지 준비

    Cloud Vision을 호출하려면 이미지 형식이 base64로 인코딩된 문자열이어야 합니다. 저장된 파일 URI에서 이미지를 처리하려면:
    1. 이미지를 Bitmap 객체로 가져옵니다.

      Java

      Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), uri);

      Kotlin+KTX

      var bitmap: Bitmap = MediaStore.Images.Media.getBitmap(contentResolver, uri)
    2. 선택적으로 이미지를 축소하여 대역폭을 절약합니다. Cloud Vision 권장 이미지 크기를 참조하세요.

      Java

      private Bitmap scaleBitmapDown(Bitmap bitmap, int maxDimension) {
          int originalWidth = bitmap.getWidth();
          int originalHeight = bitmap.getHeight();
          int resizedWidth = maxDimension;
          int resizedHeight = maxDimension;
      
          if (originalHeight > originalWidth) {
              resizedHeight = maxDimension;
              resizedWidth = (int) (resizedHeight * (float) originalWidth / (float) originalHeight);
          } else if (originalWidth > originalHeight) {
              resizedWidth = maxDimension;
              resizedHeight = (int) (resizedWidth * (float) originalHeight / (float) originalWidth);
          } else if (originalHeight == originalWidth) {
              resizedHeight = maxDimension;
              resizedWidth = maxDimension;
          }
          return Bitmap.createScaledBitmap(bitmap, resizedWidth, resizedHeight, false);
      }

      Kotlin+KTX

      private fun scaleBitmapDown(bitmap: Bitmap, maxDimension: Int): Bitmap {
          val originalWidth = bitmap.width
          val originalHeight = bitmap.height
          var resizedWidth = maxDimension
          var resizedHeight = maxDimension
          if (originalHeight > originalWidth) {
              resizedHeight = maxDimension
              resizedWidth =
                      (resizedHeight * originalWidth.toFloat() / originalHeight.toFloat()).toInt()
          } else if (originalWidth > originalHeight) {
              resizedWidth = maxDimension
              resizedHeight =
                      (resizedWidth * originalHeight.toFloat() / originalWidth.toFloat()).toInt()
          } else if (originalHeight == originalWidth) {
              resizedHeight = maxDimension
              resizedWidth = maxDimension
          }
          return Bitmap.createScaledBitmap(bitmap, resizedWidth, resizedHeight, false)
      }

      Java

      // Scale down bitmap size
      bitmap = scaleBitmapDown(bitmap, 640);

      Kotlin+KTX

      // Scale down bitmap size
      bitmap = scaleBitmapDown(bitmap, 640)
    3. 비트맵 객체를 base64로 인코딩된 문자열로 변환합니다.

      Java

      // Convert bitmap to base64 encoded string
      ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
      bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream);
      byte[] imageBytes = byteArrayOutputStream.toByteArray();
      String base64encoded = Base64.encodeToString(imageBytes, Base64.NO_WRAP);

      Kotlin+KTX

      // Convert bitmap to base64 encoded string
      val byteArrayOutputStream = ByteArrayOutputStream()
      bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream)
      val imageBytes: ByteArray = byteArrayOutputStream.toByteArray()
      val base64encoded = Base64.encodeToString(imageBytes, Base64.NO_WRAP)
    4. Bitmap 개체가 나타내는 이미지는 추가 회전 없이 똑바로 세워야 합니다.

    2. 호출 가능한 함수를 호출하여 이미지에 레이블 지정

    이미지의 객체에 레이블을 지정하려면 JSON Cloud Vision 요청 을 전달하는 호출 가능한 함수를 호출합니다.

    1. 먼저 Cloud Functions의 인스턴스를 초기화합니다.

      Java

      private FirebaseFunctions mFunctions;
      // ...
      mFunctions = FirebaseFunctions.getInstance();
      

      Kotlin+KTX

      private lateinit var functions: FirebaseFunctions
      // ...
      functions = Firebase.functions
      
    2. 함수를 호출하는 방법을 정의합니다.

      Java

      private Task<JsonElement> annotateImage(String requestJson) {
          return mFunctions
                  .getHttpsCallable("annotateImage")
                  .call(requestJson)
                  .continueWith(new Continuation<HttpsCallableResult, JsonElement>() {
                      @Override
                      public JsonElement then(@NonNull Task<HttpsCallableResult> task) {
                          // This continuation runs on either success or failure, but if the task
                          // has failed then getResult() will throw an Exception which will be
                          // propagated down.
                          return JsonParser.parseString(new Gson().toJson(task.getResult().getData()));
                      }
                  });
      }
      

      Kotlin+KTX

      private fun annotateImage(requestJson: String): Task<JsonElement> {
          return functions
                  .getHttpsCallable("annotateImage")
                  .call(requestJson)
                  .continueWith { task ->
                      // This continuation runs on either success or failure, but if the task
                      // has failed then result will throw an Exception which will be
                      // propagated down.
                      val result = task.result?.data
                      JsonParser.parseString(Gson().toJson(result))
                  }
      }
      
    3. 유형LABEL_DETECTION 으로 설정된 JSON 요청을 생성합니다.

      Java

      // Create json request to cloud vision
      JsonObject request = new JsonObject();
      // Add image to request
      JsonObject image = new JsonObject();
      image.add("content", new JsonPrimitive(base64encoded));
      request.add("image", image);
      //Add features to the request
      JsonObject feature = new JsonObject();
      feature.add("maxResults", new JsonPrimitive(5));
      feature.add("type", new JsonPrimitive("LABEL_DETECTION"));
      JsonArray features = new JsonArray();
      features.add(feature);
      request.add("features", features);
      

      Kotlin+KTX

      // Create json request to cloud vision
      val request = JsonObject()
      // Add image to request
      val image = JsonObject()
      image.add("content", JsonPrimitive(base64encoded))
      request.add("image", image)
      //Add features to the request
      val feature = JsonObject()
      feature.add("maxResults", JsonPrimitive(5))
      feature.add("type", JsonPrimitive("LABEL_DETECTION"))
      val features = JsonArray()
      features.add(feature)
      request.add("features", features)
      
    4. 마지막으로 함수를 호출합니다.

      Java

      annotateImage(request.toString())
              .addOnCompleteListener(new OnCompleteListener<JsonElement>() {
                  @Override
                  public void onComplete(@NonNull Task<JsonElement> task) {
                      if (!task.isSuccessful()) {
                          // Task failed with an exception
                          // ...
                      } else {
                          // Task completed successfully
                          // ...
                      }
                  }
              });
      

      Kotlin+KTX

      annotateImage(request.toString())
              .addOnCompleteListener { task ->
                  if (!task.isSuccessful) {
                      // Task failed with an exception
                      // ...
                  } else {
                      // Task completed successfully
                      // ...
                  }
              }
      

    3. 레이블이 지정된 개체에 대한 정보 가져오기

    이미지 레이블 지정 작업이 성공하면 BatchAnnotateImagesResponse 의 JSON 응답이 작업 결과에 반환됩니다. labelAnnotations 배열의 각 객체는 이미지에서 레이블이 지정된 항목을 나타냅니다. 각 레이블에 대해 레이블의 텍스트 설명, 지식 정보 항목 ID (사용 가능한 경우) 및 일치의 신뢰도 점수를 얻을 수 있습니다. 예를 들어:

    Java

    for (JsonElement label : task.getResult().getAsJsonArray().get(0).getAsJsonObject().get("labelAnnotations").getAsJsonArray()) {
        JsonObject labelObj = label.getAsJsonObject();
        String text = labelObj.get("description").getAsString();
        String entityId = labelObj.get("mid").getAsString();
        float score = labelObj.get("score").getAsFloat();
    }
    

    Kotlin+KTX

    for (label in task.result!!.asJsonArray[0].asJsonObject["labelAnnotations"].asJsonArray) {
        val labelObj = label.asJsonObject
        val text = labelObj["description"]
        val entityId = labelObj["mid"]
        val confidence = labelObj["score"]
    }