Pic-a-day: Lab 1 - ذخیره و تجزیه و تحلیل تصاویر (جاوا)

1. بررسی اجمالی

در اولین آزمایشگاه کد، تصاویر را در یک سطل آپلود خواهید کرد. این یک رویداد ایجاد فایل ایجاد می کند که توسط یک تابع مدیریت می شود. این تابع با Vision API تماس می گیرد تا تجزیه و تحلیل تصویر را انجام دهد و نتایج را در یک دیتا استور ذخیره کند.

d650ca5386ea71ad.png

چیزی که یاد خواهید گرفت

  • فضای ذخیره سازی ابری
  • توابع ابری
  • Cloud Vision API
  • Cloud Firestore

2. راه اندازی و الزامات

تنظیم محیط خود به خود

  1. به Google Cloud Console وارد شوید و یک پروژه جدید ایجاد کنید یا از یک موجود استفاده مجدد کنید. اگر قبلاً یک حساب Gmail یا Google Workspace ندارید، باید یک حساب ایجاد کنید .

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • نام پروژه نام نمایشی برای شرکت کنندگان این پروژه است. این یک رشته کاراکتری است که توسط API های Google استفاده نمی شود. شما می توانید آن را در هر زمان به روز کنید.
  • شناسه پروژه باید در تمام پروژه‌های Google Cloud منحصربه‌فرد باشد و تغییرناپذیر باشد (پس از تنظیم نمی‌توان آن را تغییر داد). Cloud Console به طور خودکار یک رشته منحصر به فرد تولید می کند. معمولاً برای شما مهم نیست که چیست. در اکثر کدها، باید به شناسه پروژه ارجاع دهید (معمولاً به عنوان PROJECT_ID شناخته می شود). اگر شناسه تولید شده را دوست ندارید، ممکن است یک شناسه تصادفی دیگر ایجاد کنید. از طرف دیگر، می‌توانید خودتان را امتحان کنید و ببینید آیا در دسترس است یا خیر. پس از این مرحله نمی توان آن را تغییر داد و در طول مدت پروژه باقی می ماند.
  • برای اطلاع شما، یک مقدار سوم وجود دارد، یک شماره پروژه که برخی از API ها از آن استفاده می کنند. در مورد هر سه این مقادیر در مستندات بیشتر بیاموزید.
  1. در مرحله بعد، برای استفاده از منابع Cloud/APIها باید صورتحساب را در کنسول Cloud فعال کنید . اجرا کردن از طریق این کد لبه نباید هزینه زیادی داشته باشد، اگر اصلاً باشد. برای اینکه منابع را خاموش کنید تا بیش از این آموزش متحمل صورتحساب نشوید، می توانید منابعی را که ایجاد کرده اید حذف کنید یا کل پروژه را حذف کنید. کاربران جدید Google Cloud واجد شرایط برنامه آزمایشی رایگان 300 دلاری هستند.

Cloud Shell را راه اندازی کنید

در حالی که Google Cloud را می توان از راه دور از لپ تاپ شما کار کرد، در این کد لبه از Google Cloud Shell استفاده خواهید کرد، یک محیط خط فرمان که در Cloud اجرا می شود.

از Google Cloud Console ، روی نماد Cloud Shell در نوار ابزار بالا سمت راست کلیک کنید:

55efc1aaa7a4d3ad.png

تهیه و اتصال به محیط فقط چند لحظه طول می کشد. وقتی تمام شد، باید چیزی شبیه به این را ببینید:

7ffe5cbb04455448.png

این ماشین مجازی با تمام ابزارهای توسعه که شما نیاز دارید بارگذاری شده است. این یک فهرست اصلی 5 گیگابایتی دائمی را ارائه می دهد و در Google Cloud اجرا می شود و عملکرد و احراز هویت شبکه را تا حد زیادی افزایش می دهد. تمام کارهای شما در این کد لبه را می توان در یک مرورگر انجام داد. شما نیازی به نصب چیزی ندارید.

3. API ها را فعال کنید

برای این آزمایشگاه، از Cloud Functions و Vision API استفاده خواهید کرد، اما ابتدا باید آنها را یا در Cloud Console یا با gcloud فعال کنید.

برای فعال کردن Vision API در Cloud Console، Cloud Vision API را در نوار جستجو جستجو کنید:

cf48b1747ba6a6fb.png

شما در صفحه Cloud Vision API قرار خواهید گرفت:

ba4af419e6086fbb.png

روی دکمه ENABLE کلیک کنید.

همچنین می‌توانید با استفاده از ابزار خط فرمان gcloud آن را فعال کنید.

در داخل Cloud Shell، دستور زیر را اجرا کنید:

gcloud services enable vision.googleapis.com

برای اتمام موفقیت آمیز باید عملیات را مشاهده کنید:

Operation "operations/acf.12dba18b-106f-4fd2-942d-fea80ecc5c1c" finished successfully.

عملکردهای ابری را نیز فعال کنید:

gcloud services enable cloudfunctions.googleapis.com

4. ایجاد سطل (کنسول)

یک سطل ذخیره سازی برای تصاویر ایجاد کنید. می توانید این کار را از کنسول Google Cloud Platform ( consol.cloud.google.com ) یا با ابزار خط فرمان gsutil از Cloud Shell یا محیط توسعه محلی خود انجام دهید.

از منوی "همبرگر" (☰)، به صفحه Storage بروید.

1930e055d138150a.png

سطل خود را نام ببرید

روی دکمه CREATE BUCKET کلیک کنید.

34147939358517f8.png

CONTINUE کلیک کنید.

مکان را انتخاب کنید

197817f20be07678.png

یک سطل چند منطقه ای در منطقه مورد نظر خود ایجاد کنید (اینجا Europe ).

CONTINUE کلیک کنید.

کلاس ذخیره سازی پیش فرض را انتخاب کنید

53cd91441c8caf0e.png

کلاس ذخیره سازی Standard را برای داده های خود انتخاب کنید.

CONTINUE کلیک کنید.

کنترل دسترسی را تنظیم کنید

8c2b3b459d934a51.png

از آنجایی که با تصاویر در دسترس عموم کار می کنید، می خواهید همه تصاویر ذخیره شده ما در این سطل کنترل دسترسی یکسانی داشته باشند.

گزینه Uniform Access Control را انتخاب کنید.

CONTINUE کلیک کنید.

حفاظت/رمزگذاری را تنظیم کنید

d931c24c3e705a68.png

پیش فرض ( Google-managed key) را حفظ کنید، زیرا از کلیدهای رمزگذاری خود استفاده نخواهید کرد.

روی CREATE کلیک کنید تا در نهایت ایجاد سطل ما نهایی شود.

allUsers را به عنوان نمایشگر ذخیره سازی اضافه کنید

به برگه Permissions بروید:

d0ecfdcff730ea51.png

یک عضو allUsers را با نقش Storage > Storage Object Viewer به سطل اضافه کنید:

e9f25ec1ea0b6cc6.png

روی SAVE کلیک کنید.

5. سطل (gsutil) را ایجاد کنید

همچنین می توانید از ابزار خط فرمان gsutil در Cloud Shell برای ایجاد سطل استفاده کنید.

در Cloud Shell، یک متغیر برای نام سطل یکتا تنظیم کنید. Cloud Shell قبلاً GOOGLE_CLOUD_PROJECT را روی شناسه پروژه منحصر به فرد شما تنظیم کرده است. می توانید آن را به نام سطل اضافه کنید.

به عنوان مثال:

export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}

ایجاد یک منطقه استاندارد چند منطقه ای در اروپا:

gsutil mb -l EU gs://${BUCKET_PICTURES}

از دسترسی یکنواخت به سطح سطل اطمینان حاصل کنید:

gsutil uniformbucketlevelaccess set on gs://${BUCKET_PICTURES}

سطل را عمومی کنید:

gsutil iam ch allUsers:objectViewer gs://${BUCKET_PICTURES}

اگر به بخش Cloud Storage کنسول بروید، باید یک سطل uploaded-pictures عمومی داشته باشید:

a98ed4ba17873e40.png

تست کنید که می توانید تصاویر را در سطل آپلود کنید و تصاویر آپلود شده برای عموم در دسترس هستند، همانطور که در مرحله قبل توضیح داده شد.

6. دسترسی عمومی به سطل را آزمایش کنید

با بازگشت به مرورگر ذخیره سازی، سطل خود را با دسترسی «عمومی» (شامل یک علامت هشدار که به شما یادآوری می کند که هر کسی به محتوای آن سطل دسترسی دارد) را در لیست می بینید.

89e7a4d2c80a0319.png

اکنون سطل شما برای دریافت تصاویر آماده است.

اگر روی نام سطل کلیک کنید، جزئیات سطل را مشاهده خواهید کرد.

131387f12d3eb2d3.png

در آنجا، می‌توانید دکمه Upload files را امتحان کنید تا آزمایش کنید که می‌توانید تصویری را به سطل اضافه کنید. یک پنجره انتخابگر فایل از شما می خواهد که یک فایل را انتخاب کنید. پس از انتخاب، در سطل شما آپلود می شود و دوباره دسترسی public را که به طور خودکار به این فایل جدید نسبت داده شده است، خواهید دید.

e87584471a6e9c6d.png

در کنار برچسب دسترسی Public ، نماد پیوند کوچکی را نیز مشاهده خواهید کرد. وقتی روی آن کلیک می کنید، مرورگر شما به آدرس عمومی آن تصویر می رود که به شکل زیر خواهد بود:

https://storage.googleapis.com/BUCKET_NAME/PICTURE_FILE.png

با BUCKET_NAME نام منحصربه‌فرد جهانی است که برای سطل خود انتخاب کرده‌اید، و سپس نام فایل تصویرتان.

با کلیک بر روی کادر کنار نام تصویر، دکمه DELETE فعال می شود و می توانید این تصویر اول را حذف کنید.

7. تابع را ایجاد کنید

در این مرحله، تابعی ایجاد می کنید که به رویدادهای آپلود عکس واکنش نشان می دهد.

از بخش Cloud Functions کنسول Google Cloud دیدن کنید. با مراجعه به آن سرویس Cloud Functions به صورت خودکار فعال می شود.

9d29e8c026a7a53f.png

روی Create function کلیک کنید.

یک نام (مثلاً picture-uploaded ) و منطقه (به یاد داشته باشید که با انتخاب منطقه برای سطل مطابقت داشته باشد) انتخاب کنید:

4bb222633e6f278.png

دو نوع عملکرد وجود دارد:

  • توابع HTTP که می توانند از طریق URL (به عنوان مثال یک وب API) فراخوانی شوند.
  • توابع پس‌زمینه که می‌توانند توسط برخی رویدادها فعال شوند.

می‌خواهید یک تابع پس‌زمینه ایجاد کنید که هنگام آپلود یک فایل جدید در سطل Cloud Storage ما فعال شود:

d9a12fcf58f4813c.png

شما به نوع رویداد Finalize/Create علاقه مند هستید، که رویدادی است که هنگام ایجاد یا به روز رسانی یک فایل در سطل فعال می شود:

b30c8859b07dc4cb.png

سطل ایجاد شده قبلی را انتخاب کنید تا به Cloud Function ها بگویید که هنگام ایجاد / به روز رسانی فایل در این سطل خاص مطلع شوند:

cb15a1f4c7a1ca5f.png

برای انتخاب سطلی که قبلا ایجاد کرده اید، روی Select کلیک کنید و سپس Save انتخاب کنید

c1933777fac32c6a.png

قبل از اینکه روی Next کلیک کنید، می‌توانید پیش‌فرض‌ها (حافظه 256 مگابایتی) را در قسمت Runtime، ساخت، اتصالات و تنظیمات امنیتی گسترش و تغییر دهید و آن را به 1 گیگابایت به‌روزرسانی کنید.

83d757e6c38e10.png

پس از کلیک بر روی Next ، می توانید زمان اجرا ، کد منبع و نقطه ورودی را تنظیم کنید.

Inline editor برای این تابع نگه دارید:

b6646ec646082b32.png

یکی از زمان های اجرا جاوا را انتخاب کنید، به عنوان مثال جاوا 11:

f85b8a6f951f47a7.png

کد منبع شامل یک فایل Java و یک فایل pom.xml Maven است که متادیتا و وابستگی های مختلفی را ارائه می دهد.

قطعه پیش‌فرض کد را بگذارید: نام فایل تصویر آپلود شده را ثبت می‌کند:

9b7b9801b42f6ca6.png

در حال حاضر، برای اهداف آزمایشی، نام تابع را برای اجرا در Example نگه دارید.

برای ایجاد و استقرار تابع بر روی Deploy کلیک کنید. هنگامی که استقرار با موفقیت انجام شد، باید یک علامت دایره سبز در لیست توابع مشاهده کنید:

3732fdf409eefd1a.png

8. تابع را تست کنید

در این مرحله تست کنید که تابع به رویدادهای ذخیره سازی پاسخ می دهد.

از منوی "همبرگر" (☰)، به صفحه Storage برگردید.

روی سطل تصاویر کلیک کنید و سپس بر روی Upload files کلیک کنید تا یک تصویر آپلود شود.

21767ec3cb8b18de.png

دوباره در کنسول ابری حرکت کنید تا به صفحه Logging > Logs Explorer بروید.

در انتخابگر Log Fields ، Cloud Function انتخاب کنید تا گزارش های اختصاص داده شده به عملکردهای خود را ببینید. در قسمت Log Fields به پایین اسکرول کنید و حتی می توانید یک تابع خاص را انتخاب کنید تا نمای دقیق تری از گزارش های مربوط به توابع داشته باشید. عملکرد picture-uploaded انتخاب کنید.

شما باید موارد گزارش را مشاهده کنید که به ایجاد تابع، زمان شروع و پایان تابع و بیانیه گزارش واقعی ما اشاره می کند:

e8ba7d39c36df36c.png

بیانیه گزارش ما چنین می‌گوید: Processing file: pic-a-daily-architecture-events.png ، به این معنی که رویداد مربوط به ایجاد و ذخیره‌سازی این تصویر واقعاً همانطور که انتظار می‌رفت راه‌اندازی شده است.

9. پایگاه داده را آماده کنید

شما اطلاعات مربوط به تصویر ارائه شده توسط Vision API را در پایگاه داده Cloud Firestore ، یک پایگاه داده اسناد NoSQL سریع، کاملا مدیریت شده، بدون سرور و بومی ابری ذخیره خواهید کرد. با رفتن به بخش Firestore در Cloud Console پایگاه داده خود را آماده کنید:

9e4708d2257de058.png

دو گزینه ارائه می شود: Native mode یا Datastore mode . از حالت بومی استفاده کنید، که ویژگی‌های اضافی مانند پشتیبانی آفلاین و همگام‌سازی بلادرنگ را ارائه می‌دهد.

روی SELECT NATIVE MODE کلیک کنید.

9449ace8cc84de43.png

یک منطقه چندگانه (اینجا در اروپا، اما در حالت ایده آل حداقل همان منطقه ای که عملکرد و سطل ذخیره سازی شما هستند) انتخاب کنید.

روی دکمه CREATE DATABASE کلیک کنید.

پس از ایجاد پایگاه داده، باید موارد زیر را مشاهده کنید:

56265949a124819e.png

با کلیک بر روی دکمه + START COLLECTION یک مجموعه جدید ایجاد کنید.

pictures مجموعه نام .

75806ee24c4e13a7.png

شما نیازی به ایجاد سند ندارید. وقتی تصاویر جدید در Cloud Storage ذخیره می شوند و توسط Vision API تجزیه و تحلیل می شوند، آنها را به صورت برنامه نویسی اضافه خواهید کرد.

روی Save کلیک کنید.

Firestore اولین سند پیش فرض را در مجموعه جدید ایجاد می کند، می توانید با خیال راحت آن سند را حذف کنید زیرا حاوی اطلاعات مفیدی نیست:

5c2f1e17ea47f48f.png

اسنادی که به صورت برنامه نویسی در مجموعه ما ایجاد می شوند دارای 4 فیلد خواهند بود:

  • نام (رشته): نام فایل تصویر آپلود شده که کلید سند نیز می باشد
  • برچسب ها (آرایه رشته ها): برچسب های موارد شناسایی شده توسط Vision API
  • رنگ (رشته): کد رنگ هگزادسیمال رنگ غالب (یعنی #ab12ef)
  • ایجاد (تاریخ): مُهر زمانی ذخیره شدن فراداده این تصویر
  • تصویر بندانگشتی (بولی): یک فیلد اختیاری که در صورت ایجاد یک تصویر کوچک برای این تصویر وجود دارد و درست است.

از آنجایی که در Firestore برای یافتن تصاویری که دارای ریز عکس‌ها هستند جستجو می‌کنیم و بر اساس تاریخ ایجاد مرتب می‌کنیم، باید یک فهرست جستجو ایجاد کنیم.

می توانید با دستور زیر در Cloud Shell ایندکس را ایجاد کنید:

gcloud firestore indexes composite create \
  --collection-group=pictures \
  --field-config field-path=thumbnail,order=descending \
  --field-config field-path=created,order=descending

یا می‌توانید این کار را از کنسول Cloud انجام دهید، با کلیک بر روی Indexes ، در ستون پیمایش در سمت چپ، و سپس ایجاد یک نمایه ترکیبی مانند شکل زیر:

ecb8b95e3c791272.png

روی Create کلیک کنید. ایجاد فهرست ممکن است چند دقیقه طول بکشد.

10. عملکرد را به روز کنید

به صفحه Functions برگردید، برای به روز رسانی عملکرد برای فراخوانی Vision API برای تجزیه و تحلیل تصاویر و ذخیره ابرداده ها در Firestore.

از منوی "همبرگر" (☰)، به بخش Cloud Functions بروید، روی نام تابع کلیک کنید، برگه Source را انتخاب کنید و سپس روی دکمه EDIT کلیک کنید.

ابتدا فایل pom.xml را ویرایش کنید که وابستگی های تابع جاوا را فهرست می کند. برای افزودن وابستگی به Cloud Vision API Maven، کد را به‌روزرسانی کنید:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>cloudfunctions</groupId>
  <artifactId>gcs-function</artifactId>
  <version>1.0-SNAPSHOT</version>

  <properties>
    <maven.compiler.target>11</maven.compiler.target>
    <maven.compiler.source>11</maven.compiler.source>
  </properties>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>com.google.cloud</groupId>
        <artifactId>libraries-bom</artifactId>
        <version>26.1.1</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <dependencies>
    <dependency>
      <groupId>com.google.cloud.functions</groupId>
      <artifactId>functions-framework-api</artifactId>
      <version>1.0.4</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>google-cloud-firestore</artifactId>
    </dependency>
    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>google-cloud-vision</artifactId>
    </dependency>
    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>google-cloud-storage</artifactId>
    </dependency>
  </dependencies>

  <!-- Required for Java 11 functions in the inline editor -->
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
        <configuration>
          <excludes>
            <exclude>.google/</exclude>
          </excludes>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

اکنون که وابستگی ها به روز هستند، با به روز رسانی فایل Example.java با کد سفارشی ما، روی کد تابع ما کار می کنید.

ماوس را روی فایل Example.java ببرید و روی مداد کلیک کنید. نام بسته و نام فایل را با src/main/java/fn/ImageAnalysis.java جایگزین کنید.

کد زیر را جایگزین کد ImageAnalysis.java کنید. در مرحله بعد توضیح داده خواهد شد.

package fn;

import com.google.cloud.functions.*;
import com.google.cloud.vision.v1.*;
import com.google.cloud.vision.v1.Feature.Type;
import com.google.cloud.firestore.*;
import com.google.api.core.ApiFuture;

import java.io.*;
import java.util.*;
import java.util.stream.*;
import java.util.concurrent.*;
import java.util.logging.Logger;

import fn.ImageAnalysis.GCSEvent;

public class ImageAnalysis implements BackgroundFunction<GCSEvent> {
    private static final Logger logger = Logger.getLogger(ImageAnalysis.class.getName());

    @Override
    public void accept(GCSEvent event, Context context) 
            throws IOException, InterruptedException, ExecutionException {
        String fileName = event.name;
        String bucketName = event.bucket;

        logger.info("New picture uploaded " + fileName);

        try (ImageAnnotatorClient vision = ImageAnnotatorClient.create()) {
            List<AnnotateImageRequest> requests = new ArrayList<>();
            
            ImageSource imageSource = ImageSource.newBuilder()
                .setGcsImageUri("gs://" + bucketName + "/" + fileName)
                .build();

            Image image = Image.newBuilder()
                .setSource(imageSource)
                .build();

            Feature featureLabel = Feature.newBuilder()
                .setType(Type.LABEL_DETECTION)
                .build();
            Feature featureImageProps = Feature.newBuilder()
                .setType(Type.IMAGE_PROPERTIES)
                .build();
            Feature featureSafeSearch = Feature.newBuilder()
                .setType(Type.SAFE_SEARCH_DETECTION)
                .build();
                
            AnnotateImageRequest request = AnnotateImageRequest.newBuilder()
                .addFeatures(featureLabel)
                .addFeatures(featureImageProps)
                .addFeatures(featureSafeSearch)
                .setImage(image)
                .build();
            
            requests.add(request);

            logger.info("Calling the Vision API...");
            BatchAnnotateImagesResponse result = vision.batchAnnotateImages(requests);
            List<AnnotateImageResponse> responses = result.getResponsesList();

            if (responses.size() == 0) {
                logger.info("No response received from Vision API.");
                return;
            }

            AnnotateImageResponse response = responses.get(0);
            if (response.hasError()) {
                logger.info("Error: " + response.getError().getMessage());
                return;
            }

            List<String> labels = response.getLabelAnnotationsList().stream()
                .map(annotation -> annotation.getDescription())
                .collect(Collectors.toList());
            logger.info("Annotations found:");
            for (String label: labels) {
                logger.info("- " + label);
            }

            String mainColor = "#FFFFFF";
            ImageProperties imgProps = response.getImagePropertiesAnnotation();
            if (imgProps.hasDominantColors()) {
                DominantColorsAnnotation colorsAnn = imgProps.getDominantColors();
                ColorInfo colorInfo = colorsAnn.getColors(0);

                mainColor = rgbHex(
                    colorInfo.getColor().getRed(), 
                    colorInfo.getColor().getGreen(), 
                    colorInfo.getColor().getBlue());

                logger.info("Color: " + mainColor);
            }

            boolean isSafe = false;
            if (response.hasSafeSearchAnnotation()) {
                SafeSearchAnnotation safeSearch = response.getSafeSearchAnnotation();

                isSafe = Stream.of(
                    safeSearch.getAdult(), safeSearch.getMedical(), safeSearch.getRacy(),
                    safeSearch.getSpoof(), safeSearch.getViolence())
                .allMatch( likelihood -> 
                    likelihood != Likelihood.LIKELY && likelihood != Likelihood.VERY_LIKELY
                );

                logger.info("Safe? " + isSafe);
            }

            // Saving result to Firestore
            if (isSafe) {
                FirestoreOptions firestoreOptions = FirestoreOptions.getDefaultInstance();
                Firestore pictureStore = firestoreOptions.getService();

                DocumentReference doc = pictureStore.collection("pictures").document(fileName);

                Map<String, Object> data = new HashMap<>();
                data.put("labels", labels);
                data.put("color", mainColor);
                data.put("created", new Date());

                ApiFuture<WriteResult> writeResult = doc.set(data, SetOptions.merge());

                logger.info("Picture metadata saved in Firestore at " + writeResult.get().getUpdateTime());
            }
        }
    }

    private static String rgbHex(float red, float green, float blue) {
        return String.format("#%02x%02x%02x", (int)red, (int)green, (int)blue);
    }

    public static class GCSEvent {
        String bucket;
        String name;
    }
}

968749236c3f01da.png

11. تابع را کاوش کنید

بیایید نگاهی دقیق تر به بخش های مختلف جالب داشته باشیم.

ابتدا، ما وابستگی های خاص را در فایل Maven pom.xml قرار می دهیم. Google Java Client Libraries برای حذف هرگونه تضاد وابستگی، یک Bill-of-Materials(BOM) منتشر می کند. با استفاده از آن، لازم نیست نسخه ای را برای کتابخانه های مشتری Google اختصاص دهید

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>com.google.cloud</groupId>
        <artifactId>libraries-bom</artifactId>
        <version>26.1.1</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

سپس، یک کلاینت برای Vision API آماده می کنیم:

...
try (ImageAnnotatorClient vision = ImageAnnotatorClient.create()) {
...

اکنون ساختار تابع ما می آید. ما از رویداد ورودی فیلدهایی را که به آنها علاقه مندیم می گیریم و آنها را به ساختار GCSEvent که تعریف می کنیم نگاشت می کنیم:

...
public class ImageAnalysis implements BackgroundFunction<GCSEvent> {
    @Override
    public void accept(GCSEvent event, Context context) 
            throws IOException, InterruptedException,     
    ExecutionException {
...

    public static class GCSEvent {
        String bucket;
        String name;
    }

به امضا، و همچنین نحوه بازیابی نام فایل و سطلی که عملکرد Cloud را راه اندازی کرده است، توجه کنید.

برای مرجع، در اینجا به چه صورت است payload رویداد:

{
  "bucket":"uploaded-pictures",
  "contentType":"image/png",
  "crc32c":"efhgyA==",
  "etag":"CKqB956MmucCEAE=",
  "generation":"1579795336773802",
  "id":"uploaded-pictures/Screenshot.png/1579795336773802",
  "kind":"storage#object",
  "md5Hash":"PN8Hukfrt6C7IyhZ8d3gfQ==",
  "mediaLink":"https://www.googleapis.com/download/storage/v1/b/uploaded-pictures/o/Screenshot.png?generation=1579795336773802&alt=media",
  "metageneration":"1",
  "name":"Screenshot.png",
  "selfLink":"https://www.googleapis.com/storage/v1/b/uploaded-pictures/o/Screenshot.png",
  "size":"173557",
  "storageClass":"STANDARD",
  "timeCreated":"2020-01-23T16:02:16.773Z",
  "timeStorageClassUpdated":"2020-01-23T16:02:16.773Z",
  "updated":"2020-01-23T16:02:16.773Z"
}

ما درخواستی را برای ارسال از طریق مشتری Vision آماده می کنیم:

ImageSource imageSource = ImageSource.newBuilder()
    .setGcsImageUri("gs://" + bucketName + "/" + fileName)
    .build();

Image image = Image.newBuilder()
    .setSource(imageSource)
    .build();

Feature featureLabel = Feature.newBuilder()
    .setType(Type.LABEL_DETECTION)
    .build();
Feature featureImageProps = Feature.newBuilder()
    .setType(Type.IMAGE_PROPERTIES)
    .build();
Feature featureSafeSearch = Feature.newBuilder()
    .setType(Type.SAFE_SEARCH_DETECTION)
    .build();
    
AnnotateImageRequest request = AnnotateImageRequest.newBuilder()
    .addFeatures(featureLabel)
    .addFeatures(featureImageProps)
    .addFeatures(featureSafeSearch)
    .setImage(image)
    .build();

ما 3 قابلیت کلیدی Vision API را درخواست می کنیم:

  • تشخیص برچسب : برای درک آنچه در آن تصاویر وجود دارد
  • ویژگی های تصویر : برای دادن ویژگی های جالب به تصویر (ما به رنگ غالب تصویر علاقه مند هستیم)
  • جستجوی ایمن : برای اطلاع از اینکه آیا تصویر برای نمایش بی خطر است (این تصویر نباید حاوی محتوای بزرگسالان / پزشکی / نژادی / خشونت آمیز باشد)

در این مرحله، می‌توانیم با Vision API تماس بگیریم:

...
logger.info("Calling the Vision API...");
BatchAnnotateImagesResponse result = 
                            vision.batchAnnotateImages(requests);
List<AnnotateImageResponse> responses = result.getResponsesList();
...

برای مرجع، در اینجا پاسخ از Vision API به نظر می رسد:

{
  "faceAnnotations": [],
  "landmarkAnnotations": [],
  "logoAnnotations": [],
  "labelAnnotations": [
    {
      "locations": [],
      "properties": [],
      "mid": "/m/01yrx",
      "locale": "",
      "description": "Cat",
      "score": 0.9959855675697327,
      "confidence": 0,
      "topicality": 0.9959855675697327,
      "boundingPoly": null
    },
     - - - 
  ],
  "textAnnotations": [],
  "localizedObjectAnnotations": [],
  "safeSearchAnnotation": {
    "adult": "VERY_UNLIKELY",
    "spoof": "UNLIKELY",
    "medical": "VERY_UNLIKELY",
    "violence": "VERY_UNLIKELY",
    "racy": "VERY_UNLIKELY",
    "adultConfidence": 0,
    "spoofConfidence": 0,
    "medicalConfidence": 0,
    "violenceConfidence": 0,
    "racyConfidence": 0,
    "nsfwConfidence": 0
  },
  "imagePropertiesAnnotation": {
    "dominantColors": {
      "colors": [
        {
          "color": {
            "red": 203,
            "green": 201,
            "blue": 201,
            "alpha": null
          },
          "score": 0.4175916016101837,
          "pixelFraction": 0.44456374645233154
        },
         - - - 
      ]
    }
  },
  "error": null,
  "cropHintsAnnotation": {
    "cropHints": [
      {
        "boundingPoly": {
          "vertices": [
            { "x": 0, "y": 118 },
            { "x": 1177, "y": 118 },
            { "x": 1177, "y": 783 },
            { "x": 0, "y": 783 }
          ],
          "normalizedVertices": []
        },
        "confidence": 0.41695669293403625,
        "importanceFraction": 1
      }
    ]
  },
  "fullTextAnnotation": null,
  "webDetection": null,
  "productSearchResults": null,
  "context": null
}

اگر خطایی برگردانده نشد، می‌توانیم ادامه دهیم، به همین دلیل این بلوک را داریم:

AnnotateImageResponse response = responses.get(0);
if (response.hasError()) {
     logger.info("Error: " + response.getError().getMessage());
     return;
}

ما می‌خواهیم برچسب‌های چیزها، دسته‌ها یا مضامین شناسایی شده در تصویر را دریافت کنیم:

List<String> labels = response.getLabelAnnotationsList().stream()
    .map(annotation -> annotation.getDescription())
    .collect(Collectors.toList());

logger.info("Annotations found:");
for (String label: labels) {
    logger.info("- " + label);
}

ما علاقه مندیم که رنگ غالب تصویر را بدانیم:

String mainColor = "#FFFFFF";
ImageProperties imgProps = response.getImagePropertiesAnnotation();
if (imgProps.hasDominantColors()) {
    DominantColorsAnnotation colorsAnn = 
                               imgProps.getDominantColors();
    ColorInfo colorInfo = colorsAnn.getColors(0);

    mainColor = rgbHex(
        colorInfo.getColor().getRed(), 
        colorInfo.getColor().getGreen(), 
        colorInfo.getColor().getBlue());

    logger.info("Color: " + mainColor);
}

ما همچنین از یک تابع ابزار برای تبدیل مقادیر قرمز / سبز / آبی به یک کد رنگ هگزادسیمال استفاده می کنیم که می توانیم در شیوه نامه های CSS استفاده کنیم.

بیایید بررسی کنیم که آیا تصویر برای نمایش امن است یا خیر:

boolean isSafe = false;
if (response.hasSafeSearchAnnotation()) {
    SafeSearchAnnotation safeSearch = 
                      response.getSafeSearchAnnotation();

    isSafe = Stream.of(
        safeSearch.getAdult(), safeSearch.getMedical(), safeSearch.getRacy(),
        safeSearch.getSpoof(), safeSearch.getViolence())
    .allMatch( likelihood -> 
        likelihood != Likelihood.LIKELY && likelihood != Likelihood.VERY_LIKELY
    );

    logger.info("Safe? " + isSafe);
}

ما در حال بررسی ویژگی‌های بزرگسالان / جعل / پزشکی / خشونت / نژادپرستانه هستیم تا ببینیم احتمال آن وجود ندارد یا بسیار محتمل است .

اگر نتیجه جستجوی ایمن درست باشد، می‌توانیم متادیتا را در Firestore ذخیره کنیم:

if (isSafe) {
    FirestoreOptions firestoreOptions = FirestoreOptions.getDefaultInstance();
    Firestore pictureStore = firestoreOptions.getService();

    DocumentReference doc = pictureStore.collection("pictures").document(fileName);

    Map<String, Object> data = new HashMap<>();
    data.put("labels", labels);
    data.put("color", mainColor);
    data.put("created", new Date());

    ApiFuture<WriteResult> writeResult = doc.set(data, SetOptions.merge());

    logger.info("Picture metadata saved in Firestore at " + writeResult.get().getUpdateTime());
}

12. تابع را مستقر کنید

زمان استقرار تابع است.

604f47aa11fbf8e.png

دکمه DEPLOY را فشار دهید و نسخه جدید مستقر می شود، می توانید پیشرفت را مشاهده کنید:

13da63f23e4dbbdd.png

13. دوباره تابع را تست کنید

پس از اجرای موفقیت آمیز تابع، تصویری را به Cloud Storage ارسال می کنید، ببینید آیا تابع ما فراخوانی شده است، Vision API چه چیزی را برمی گرداند، و آیا ابرداده در Firestore ذخیره می شود.

به Cloud Storage برگردید و روی سطلی که در ابتدای آزمایشگاه ایجاد کردیم کلیک کنید:

d44c1584122311c7.png

پس از ورود به صفحه جزئیات سطل، روی دکمه Upload files کلیک کنید تا یک عکس آپلود شود.

26bb31d35fb6aa3d.png

از منوی "همبرگر" (☰)، به Logging > Logs Explorer بروید.

در انتخابگر Log Fields ، Cloud Function انتخاب کنید تا گزارش های اختصاص داده شده به عملکردهای خود را ببینید. در قسمت Log Fields به پایین اسکرول کنید و حتی می توانید یک تابع خاص را انتخاب کنید تا نمای دقیق تری از گزارش های مربوط به توابع داشته باشید. عملکرد picture-uploaded انتخاب کنید.

b651dca7e25d5b11.png

و در واقع، در لیست گزارش ها، می توانم ببینم که تابع ما فراخوانی شده است:

d22a7f24954e4f63.png

گزارش ها شروع و پایان اجرای تابع را نشان می دهد. و در این بین، ما می توانیم گزارش هایی را که در تابع خود قرار داده ایم با دستورات console.log() ببینیم. می بینیم:

  • جزئیات رویدادی که عملکرد ما را تحریک می کند،
  • نتایج خام از فراخوانی Vision API،
  • برچسب هایی که در تصویری که آپلود کردیم یافت شد،
  • اطلاعات رنگ های غالب،
  • اینکه آیا تصویر برای نشان دادن بی خطر است،
  • و در نهایت آن ابرداده های مربوط به تصویر در Firestore ذخیره شده است.

9ff7956a215c15da.png

دوباره از منوی "همبرگر" (☰)، به بخش Firestore بروید. در زیر بخش Data (به طور پیش فرض نشان داده شده است)، باید مجموعه pictures را با یک سند جدید اضافه کنید، مطابق با تصویری که اخیراً آپلود کرده اید:

a6137ab9687da370.png

14. تمیز کردن (اختیاری)

اگر قصد ندارید با دیگر آزمایشگاه‌های این سری ادامه دهید، می‌توانید منابع را پاکسازی کنید تا در هزینه‌ها صرفه‌جویی کنید و در کل شهروند ابری خوبی باشید. می توانید منابع را به صورت جداگانه به صورت زیر پاک کنید.

سطل را حذف کنید:

gsutil rb gs://${BUCKET_PICTURES}

حذف تابع:

gcloud functions delete picture-uploaded --region europe-west1 -q

با انتخاب حذف مجموعه از مجموعه، مجموعه Firestore را حذف کنید:

410b551c3264f70a.png

یا می توانید کل پروژه را حذف کنید:

gcloud projects delete ${GOOGLE_CLOUD_PROJECT} 

15. تبریک می گویم!

تبریک می گویم! شما اولین سرویس کلیدی پروژه را با موفقیت اجرا کردید!

آنچه را پوشش داده ایم

  • فضای ذخیره سازی ابری
  • توابع ابری
  • Cloud Vision API
  • Cloud Firestore

مراحل بعدی