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

۱. مرور کلی

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

d650ca5386ea71ad.png

آنچه یاد خواهید گرفت

  • فضای ذخیره‌سازی ابری
  • توابع ابری
  • رابط برنامه‌نویسی کاربردی (API) دید ابری
  • فروشگاه ابری فایر استور

۲. تنظیمات و الزامات

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

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

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

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

شروع پوسته ابری

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

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

55efc1aaa7a4d3ad.png

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

7ffe5cbb04455448.png

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

۳. فعال کردن APIها

برای این آزمایش، شما از توابع ابری و رابط برنامه‌نویسی کاربردی بینایی (Vision API) استفاده خواهید کرد، اما ابتدا باید آنها را یا در کنسول ابری یا با gcloud فعال کنید.

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

cf48b1747ba6a6fb.png

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

ba4af419e6086fbb.png

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

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

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

gcloud services enable vision.googleapis.com

باید ببینید که عملیات با موفقیت به پایان رسیده است:

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

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

gcloud services enable cloudfunctions.googleapis.com

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

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

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

1930e055d138150a.png

سطل خود را نامگذاری کنید

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

۳۴۱۴۷۹۳۹۳۵۸۵۱۷f۸.png

CONTINUE کلیک کنید.

انتخاب مکان

۱۹۷۸۱۷f۲۰be۰۷۶۷۸.png

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

CONTINUE کلیک کنید.

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

53cd91441c8caf0e.png

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

CONTINUE کلیک کنید.

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

8c2b3b459d934a51.png

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

گزینه کنترل دسترسی Uniform را انتخاب کنید.

CONTINUE کلیک کنید.

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

d931c24c3e705a68.png

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

برای نهایی کردن ایجاد سطل، CREATE کلیک کنید.

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

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

d0ecfdcff730ea51.png

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

e9f25ec1ea0b6cc6.png

SAVE کلیک کنید.

۵. ایجاد سطل (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

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

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

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

89e7a4d2c80a0319.png

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

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

۱۳۱۳۸۷f12d3eb2d3.png

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

e87584471a6e9c6d.png

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

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

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

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

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

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

به بخش Cloud Functions در کنسول Google Cloud مراجعه کنید. با مراجعه به آن، سرویس Cloud Functions به طور خودکار فعال می‌شود.

9d29e8c026a7a53f.png

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

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

4bb222633e6f278.png

دو نوع تابع وجود دارد:

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

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

d9a12fcf58f4813c.png

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

b30c8859b07dc4cb.png

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

cb15a1f4c7a1ca5f.png

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

c1933777fac32c6a.png

قبل از اینکه روی Next کلیک کنید، می‌توانید پیش‌فرض‌ها (حافظه ۲۵۶ مگابایت) را در قسمت Runtime, build, connections and security settings گسترش داده و تغییر دهید و آن را به ۱ گیگابایت به‌روزرسانی کنید.

83d757e6c38e10.png

پس از کلیک روی Next ، می‌توانید Runtime ، Source code و entry point را تنظیم کنید.

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

b6646ec646082b32.png

یکی از زمان‌های اجرای جاوا را انتخاب کنید، مثلاً جاوا ۱۱:

f85b8a6f951f47a7.png

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

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

9b7b9801b42f6ca6.png

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

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

3732fdf409eefd1a.png

۸. تابع را آزمایش کنید

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

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

برای آپلود تصویر، روی گزینه‌ی images bucket و سپس روی Upload files کلیک کنید.

21767ec3cb8b18de.png

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

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

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

e8ba7d39c36df36c.png

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

۹. آماده‌سازی پایگاه داده

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

9e4708d2257de058.png

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

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

۹۴۴۹ace۸cc۸۴de۴۳.png

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

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

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

56265949a124819e.png

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

pictures مجموعه اسامی

75806ee24c4e13a7.png

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

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

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

5c2f1e17ea47f48f.png

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

  • نام (رشته): نام فایل تصویر آپلود شده که کلید سند نیز هست.
  • برچسب‌ها (آرایه‌ای از رشته‌ها): برچسب‌های اقلام شناسایی‌شده توسط Vision API
  • رنگ (رشته): کد رنگ هگزادسیمال رنگ غالب (مثلاً #ab12ef)
  • ایجاد شده (تاریخ): مهر زمانی ذخیره شدن فراداده‌های این تصویر
  • تصویر بندانگشتی (boolean): یک فیلد اختیاری که در صورت ایجاد تصویر بندانگشتی برای این تصویر، وجود خواهد داشت و مقدار آن true خواهد بود.

از آنجایی که ما در Firestore به دنبال تصاویری خواهیم گشت که تصویر بندانگشتی (thumbnail) آنها موجود باشد و آنها را بر اساس تاریخ ایجاد مرتب کنیم، باید یک فهرست جستجو (search index) ایجاد کنیم.

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

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

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

ecb8b95e3c791272.png

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

۱۰. تابع را به‌روزرسانی کنید

به صفحه 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;
    }
}

۹۶۸۷۴۹۲۳۶c3f01da.png

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

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

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

  <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();

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

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

در این مرحله، می‌توانیم رابط برنامه‌نویسی Vision را فراخوانی کنیم:

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

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

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

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

وقت آن رسیده که تابع را مستقر کنیم.

604f47aa11fbf8e.png

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

13da63f23e4dbbdd.png

۱۳. تابع را دوباره آزمایش کنید

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

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

d44c1584122311c7.png

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

26bb31d35fb6aa3d.png

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

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

b651dca7e25d5b11.png

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

d22a7f24954e4f63.png

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

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

9ff7956a215c15da.png

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

a6137ab9687da370.png

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

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

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

gsutil rb gs://${BUCKET_PICTURES}

تابع را حذف کنید:

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

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

410b551c3264f70a.png

روش دیگر، حذف کل پروژه است:

gcloud projects delete ${GOOGLE_CLOUD_PROJECT} 

۱۵. تبریک می‌گویم!

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

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

  • فضای ذخیره‌سازی ابری
  • توابع ابری
  • رابط برنامه‌نویسی کاربردی (API) دید ابری
  • فروشگاه ابری فایر استور

مراحل بعدی