تشخیص امن نقاط دیدنی با Cloud Vision بااستفاده از «احراز هویت Firebase» و «توابع» در Android

برای فراخوانی Google Cloud API از برنامه‌تان، باید یک میانای برنامه‌سازی کاربردی میانجی REST ایجاد کنید که مجوزدهی را مدیریت کند و از مقادیر محرمانه مثل کلیدهای API محافظت کند. سپس باید در برنامه تلفن همراهتان کد بنویسید تا با این سرویس واسطه ارتباط برقرار کنید و هویتتان را در آن به‌تأیید برسانید.

یکی از روش‌های ایجاد این REST API استفاده از «احراز هویت Firebase» و «توابع» است که درگاهی بدون سرور و مدیریت‌شده به Google Cloud APIs ارائه می‌دهد که احراز هویت را مدیریت می‌کند و می‌تواند از برنامه تلفن همراه شما با کیت‌های توسعه نرم‌افزار ازپیش ساخته‌شده فراخوانی شود.

این راهنما نشان می‌دهد که چگونه از این تکنیک برای فراخوانی Cloud Vision API از برنامه‌تان استفاده کنید. این روش به همه کاربران اصالت‌سنجی‌شده اجازه می‌دهد ازطریق پروژه Cloud شما به سرویس‌های صورت‌حساب‌دار Cloud Vision دسترسی داشته باشند، بنابراین قبل‌از ادامه دادن، درنظر بگیرید که آیا این سازوکار اصالت‌سنجی برای مورد استفاده شما کافی است یا نه.

قبل از شروع

پیکربندی پروژه

  1. اگر قبلاً این کار را نکرده‌اید، ‫Firebase را به پروژه Android خود اضافه کنید.
  2. اگر قبلاً «میاناهای برنامه‌سازی کاربردی ابری» را برای پروژه‌تان فعال نکرده‌اید، اکنون این کار را انجام دهید:

    1. صفحه Firebase ML APIs را در کنسول Firebase باز کنید.
    2. اگر هنوز پروژه‌تان را به طرح قیمت‌گذاری Blaze با پرداخت براساس مصرف ارتقا نداده‌اید، روی ارتقا کلیک کنید تا این کار را انجام دهید. (فقط درصورتی از شما خواسته می‌شود پروژه‌تان را ارتقا دهید که در طرح قیمت‌گذاری Blaze نباشد.)

      فقط پروژه‌های طرح قیمت‌گذاری Blaze می‌توانند از میاناهای برنامه‌سازی کاربردی مبتنی بر ابر استفاده کنند.

    3. اگر «میاناهای برنامه‌سازی کاربردی مبتنی بر Cloud» ازقبل فعال نشده است، روی فعال کردن میاناهای برنامه‌سازی کاربردی مبتنی بر Cloud کلیک کنید.
  3. کلیدهای Firebase API موجود را پیکربندی کنید تا دسترسی به Cloud Vision API را غیرمجاز کنید:
    1. صفحه اطلاعات اعتباری کنسول Cloud را باز کنید.
    2. برای هر کلید API در فهرست، نمای ویرایش را باز کنید و در بخش «محدودیت‌های کلید»، همه APIهای دردسترس به‌جز Cloud Vision API را به فهرست اضافه کنید.

استقرار تابع فراخوان‌شدنی

سپس، Cloud Function را که برای ایجاد پل بین برنامه و Cloud Vision API استفاده می‌کنید مستقر کنید. مخزن functions-samples حاوی نمونه‌ای است که می‌توانید از آن استفاده کنید.

به‌طور پیش‌فرض، دسترسی به Cloud Vision API ازطریق این تابع فقط به کاربران اصیل‌سازی‌شده برنامه شما اجازه می‌دهد به Cloud Vision API دسترسی داشته باشند. می‌توانید عملکرد را برای الزامات مختلف تغییر دهید.

برای استقرار تابع:

  1. مخزن functions-samples را شبیه‌سازی یا بارگیری کنید و به دایرکتوری 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» را ندارید، آن را نصب کنید.
  4. پروژه Firebase را در فهرست راهنمای vision-annotate-image مقداردهی اولیه کنید. وقتی درخواست شد، پروژه خود را در فهرست انتخاب کنید.
    firebase init
  5. استقرار تابع:
    firebase deploy --only functions:annotateImage

افزودن «احراز هویت Firebase» به برنامه

تابع فراخواندنی که در بالا مستقر شده است هرگونه درخواست از کاربران غیرتأییدشده برنامه‌تان را رد خواهد کرد. اگر قبلاً این کار را نکرده‌اید، باید «احراز هویت Firebase» را به برنامه‌تان اضافه کنید.

افزودن وابستگی‌های ضروری به برنامه

  • وابستگی‌های «توابع ابری ویژه Firebase» (کارخواه) و کتابخانه‌های Android‏ gson را به فایل Gradle واحد (سطح برنامه) (معمولاً <project>/<app-module>/build.gradle.kts یا <project>/<app-module>/build.gradle) اضافه کنید:
    implementation("com.google.firebase:firebase-functions:23.0.0")
    implementation("com.google.code.gson:gson:2.8.6")
  • ۱. آماده کردن تصویر ورودی

    برای فراخوانی Cloud Vision، تصویر باید به‌صورت رشته کدبندی‌شده با base64 قالب‌بندی شود. برای پردازش تصویر از نشانی وب فایل ذخیره‌شده:
    1. تصویر را به‌صورت شیء Bitmap دریافت کنید:

      Kotlin

      var bitmap: Bitmap = MediaStore.Images.Media.getBitmap(contentResolver, uri)

      Java

      Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), uri);
    2. درصورت تمایل، تصویر را کوچک کنید تا در پهنای باند صرفه‌جویی شود. اندازه‌های تصویر توصیه‌شده Cloud Vision را ببینید.

      Kotlin

      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

      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

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

      Java

      // Scale down bitmap size
      bitmap = scaleBitmapDown(bitmap, 640);
    3. تبدیل کردن شیء بیت‌مپ به رشته کدبندی‌شده base64:

      Kotlin

      // 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)

      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);
    4. تصویر نشان‌داده‌شده توسط شیء Bitmap باید عمودی باشد و نیازی به چرخش اضافی نداشته باشد.

    ۲. برای تشخیص نشانگرهای مکانی، تابع فراخوان‌شدنی را فراخوانی کنید

    برای تشخیص نشان‌های جغرافیایی در تصویر، تابع فراخوان را با ارسال درخواست JSON Cloud Vision فراخوانی کنید.

    1. ابتدا نمونه‌ای از Cloud Functions را مقداردهی اولیه کنید:

      Kotlin

      private lateinit var functions: FirebaseFunctions
      // ...
      functions = Firebase.functions
      

      Java

      private FirebaseFunctions mFunctions;
      // ...
      mFunctions = FirebaseFunctions.getInstance();
      
    2. روشی برای فراخوانی تابع تعریف کنید:

      Kotlin

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

      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()));
                      }
                  });
      }
      
    3. درخواستی با قالب JSON با نوع LANDMARK_DETECTION ایجاد کنید:

      Kotlin

      // 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("LANDMARK_DETECTION"))
      val features = JsonArray()
      features.add(feature)
      request.add("features", features)
      

      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("LANDMARK_DETECTION"));
      JsonArray features = new JsonArray();
      features.add(feature);
      request.add("features", features);
      
    4. درنهایت، تابع را فراخوانی کنید:

      Kotlin

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

      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
                          // ...
                      }
                  }
              });
      

    ۳. درباره نشانگرهای مکانی تشخیص‌داده‌شده اطلاعات دریافت کنید

    اگر عملیات تشخیص مکان دیدنی موفقیت‌آمیز باشد، پاسخ JSON از BatchAnnotateImagesResponse در نتیجه تکلیف برگردانده خواهد شد. هر شیء در آرایه landmarkAnnotations نشان‌دهنده یک مکان دیدنی است که در تصویر تشخیص داده شده است. برای هر نشانه، می‌توانید مختصات محدوده‌بندی آن را در تصویر ورودی، نام نشانه، عرض و طول جغرافیایی آن، شناسه نهاد «نمودار دانش» آن (درصورت وجود)، و امتیاز اطمینان مطابقت را دریافت کنید. برای مثال:

    Kotlin

    for (label in task.result!!.asJsonArray[0].asJsonObject["landmarkAnnotations"].asJsonArray) {
        val labelObj = label.asJsonObject
        val landmarkName = labelObj["description"]
        val entityId = labelObj["mid"]
        val score = labelObj["score"]
        val bounds = labelObj["boundingPoly"]
        // Multiple locations are possible, e.g., the location of the depicted
        // landmark and the location the picture was taken.
        for (loc in labelObj["locations"].asJsonArray) {
            val latitude = loc.asJsonObject["latLng"].asJsonObject["latitude"]
            val longitude = loc.asJsonObject["latLng"].asJsonObject["longitude"]
        }
    }
    

    Java

    for (JsonElement label : task.getResult().getAsJsonArray().get(0).getAsJsonObject().get("landmarkAnnotations").getAsJsonArray()) {
        JsonObject labelObj = label.getAsJsonObject();
        String landmarkName = labelObj.get("description").getAsString();
        String entityId = labelObj.get("mid").getAsString();
        float score = labelObj.get("score").getAsFloat();
        JsonObject bounds = labelObj.get("boundingPoly").getAsJsonObject();
        // Multiple locations are possible, e.g., the location of the depicted
        // landmark and the location the picture was taken.
        for (JsonElement loc : labelObj.get("locations").getAsJsonArray()) {
            JsonObject latLng = loc.getAsJsonObject().get("latLng").getAsJsonObject();
            double latitude = latLng.get("latitude").getAsDouble();
            double longitude = latLng.get("longitude").getAsDouble();
        }
    }