Pic-a-day: ذخیره و تجزیه و تحلیل تصاویر با کتابخانه های Google Native Client Java

۱. مرور کلی

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

427de3100de3a61e.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.

Cloud Run و Cloud Build را نیز فعال کنید:

gcloud services enable cloudbuild.googleapis.com \
  run.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 فعال می‌شود و می‌توانید این تصویر اول را حذف کنید.

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

شما اطلاعات مربوط به تصویر داده شده توسط 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 کلیک کنید. ایجاد فهرست می‌تواند چند دقیقه طول بکشد.

۸. کد را کپی کنید

اگر قبلاً در آزمایشگاه کد قبلی این کار را نکرده‌اید، کد را کلون کنید:

git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop

سپس می‌توانید برای شروع ساخت آزمایشگاه به دایرکتوری حاوی سرویس بروید:

cd serverless-photosharing-workshop/services/image-analysis/java

شما طرح فایل زیر را برای سرویس خواهید داشت:

f79613aff479d8ad.png

۹. کد سرویس را بررسی کنید

شما با بررسی نحوه فعال‌سازی کتابخانه‌های کلاینت جاوا در pom.xml با استفاده از BOM شروع می‌کنید:

ابتدا، فایل 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>

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

@RestController
public class EventController {
  private static final Logger logger = Logger.getLogger(EventController.class.getName());
    
  private static final List<String> requiredFields = Arrays.asList("ce-id", "ce-source", "ce-type", "ce-specversion");

  @RequestMapping(value = "/", method = RequestMethod.POST)
  public ResponseEntity<String> receiveMessage(
    @RequestBody Map<String, Object> body, @RequestHeader Map<String, String> headers) throws IOException, InterruptedException, ExecutionException {
...
}

کد به اعتبارسنجی هدرهای Cloud Events ادامه می‌دهد:

System.out.println("Header elements");
for (String field : requiredFields) {
    if (headers.get(field) == null) {
    String msg = String.format("Missing expected header: %s.", field);
    System.out.println(msg);
    return new ResponseEntity<String>(msg, HttpStatus.BAD_REQUEST);
    } else {
    System.out.println(field + " : " + headers.get(field));
    }
}

System.out.println("Body elements");
for (String bodyField : body.keySet()) {
    System.out.println(bodyField + " : " + body.get(bodyField));
}

if (headers.get("ce-subject") == null) {
    String msg = "Missing expected header: ce-subject.";
    System.out.println(msg);
    return new ResponseEntity<String>(msg, HttpStatus.BAD_REQUEST);
} 

اکنون می‌توان یک درخواست ایجاد کرد و کد، چنین درخواستی را برای ارسال به Vision API آماده می‌کند:

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

ما سه قابلیت کلیدی 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 را داریم:

if (responses.size() == 0) {
    logger.info("No response received from Vision API.");
    return new ResponseEntity<String>(msg, HttpStatus.BAD_REQUEST);
}

AnnotateImageResponse response = responses.get(0);
if (response.hasError()) {
    logger.info("Error: " + response.getError().getMessage());
    return new ResponseEntity<String>(msg, HttpStatus.BAD_REQUEST);
}

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

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

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

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

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

۱۰. ساخت تصاویر برنامه با GraalVM (اختیاری)

در این مرحله اختیاری، شما یک JIT(JVM) based app image و سپس یک AOT(Native) Java app image با استفاده از GraalVM خواهید ساخت.

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

To start ، GraalVM 22.2.x Community Edition را دانلود کنید و دستورالعمل‌های موجود در صفحه نصب GraalVM را دنبال کنید.

این فرآیند را می‌توان با کمک SDKMAN تا حد زیادی ساده کرد!

برای نصب توزیع JDK مناسب با SDKman ، با استفاده از دستور install شروع کنید:

sdk install java 22.2.r17-grl

به SDKman دستور دهید که از این نسخه، هم برای ساخت‌های JIT و هم AOT، استفاده کند:

sdk use java 22.2.0.r17-grl

native-image utility را برای GraalVM نصب کنید:

gu install native-image

برای راحتی شما، می‌توانید در Cloudshell ، GraalVM و ابزار native-image را با این دستورات ساده نصب کنید:

# install GraalVM in your home directory
cd ~

# download GraalVM
wget https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-22.2.0/graalvm-ce-java17-linux-amd64-22.2.0.tar.gz
ls
tar -xzvf graalvm-ce-java17-linux-amd64-22.2.0.tar.gz

# configure Java 17 and GraalVM 22.2
echo Existing JVM: $JAVA_HOME
cd graalvm-ce-java17-22.2.0
export JAVA_HOME=$PWD
cd bin
export PATH=$PWD:$PATH

echo JAVA HOME: $JAVA_HOME
echo PATH: $PATH

# install the native image utility
java -version
gu install native-image

cd ../..

ابتدا، متغیرهای محیطی پروژه GCP را تنظیم کنید:

export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)

سپس می‌توانید برای شروع ساخت آزمایشگاه به دایرکتوری حاوی سرویس بروید:

cd serverless-photosharing-workshop/services/image-analysis/java

ساخت ایمیج برنامه JIT(JVM):

./mvnw package -Pjvm

به گزارش ساخت در ترمینال توجه کنید:

...
[INFO] --- spring-boot-maven-plugin:2.7.3:repackage (repackage) @ image-analysis ---
[INFO] Replacing main artifact with repackaged archive
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  24.009 s
[INFO] Finished at: 2022-09-26T22:17:32-04:00
[INFO] ------------------------------------------------------------------------

ساخت ایمیج AOT(Native):

./mvnw package -Pnative -DskipTests

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

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

...
[2/7] Performing analysis...  [**********]                                                              (95.4s @ 3.57GB)
  23,346 (94.42%) of 24,725 classes reachable
  44,625 (68.71%) of 64,945 fields reachable
 163,759 (70.79%) of 231,322 methods reachable
     989 classes, 1,402 fields, and 11,032 methods registered for reflection
      63 classes,    69 fields, and    55 methods registered for JNI access
       5 native libraries: -framework CoreServices, -framework Foundation, dl, pthread, z
[3/7] Building universe...                                                                              (10.0s @ 5.35GB)
[4/7] Parsing methods...      [***]                                                                      (9.7s @ 3.13GB)
[5/7] Inlining methods...     [***]                                                                      (4.5s @ 3.29GB)
[6/7] Compiling methods...    [[6/7] Compiling methods...    [********]                                                                (67.6s @ 5.72GB)
[7/7] Creating image...                                                                                  (8.7s @ 4.59GB)
  62.21MB (54.80%) for code area:   100,371 compilation units
  50.98MB (44.91%) for image heap:  465,035 objects and 365 resources
 337.09KB ( 0.29%) for other data
 113.52MB in total
------------------------------------------------------------------------------------------------------------------------
Top 10 packages in code area:                               Top 10 object types in image heap:
   2.36MB com.google.protobuf                                 12.70MB byte[] for code metadata
   1.90MB i.g.xds.shaded.io.envoyproxy.envoy.config.core.v3    6.66MB java.lang.Class
   1.73MB i.g.x.shaded.io.envoyproxy.envoy.config.route.v3     6.47MB byte[] for embedded resources
   1.67MB sun.security.ssl                                     4.61MB byte[] for java.lang.String
   1.54MB com.google.cloud.vision.v1                           4.37MB java.lang.String
   1.46MB com.google.firestore.v1                              3.38MB byte[] for general heap data
   1.37MB io.grpc.xds.shaded.io.envoyproxy.envoy.api.v2.core   1.96MB com.oracle.svm.core.hub.DynamicHubCompanion
   1.32MB i.g.xds.shaded.io.envoyproxy.envoy.api.v2.route      1.80MB byte[] for reflection metadata
   1.09MB java.util                                          911.80KB java.lang.String[]
   1.08MB com.google.re2j                                    826.48KB c.o.svm.core.hub.DynamicHub$ReflectionMetadata
  45.91MB for 772 more packages                                6.45MB for 3913 more object types
------------------------------------------------------------------------------------------------------------------------
                        15.1s (6.8% of total time) in 56 GCs | Peak RSS: 7.72GB | CPU load: 4.37
------------------------------------------------------------------------------------------------------------------------
Produced artifacts:
 /Users/ddobrin/work/dan/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis (executable)
 /Users/ddobrin/work/dan/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis.build_artifacts.txt (txt)
========================================================================================================================
Finished generating 'image-analysis' in 3m 41s.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  03:56 min
[INFO] Finished at: 2022-09-26T22:22:29-04:00
[INFO] ------------------------------------------------------------------------

۱۱. ساخت و انتشار ایمیج‌های کانتینر

بیایید یک تصویر کانتینر در دو نسخه مختلف بسازیم: یکی به عنوان یک JIT(JVM) image و دیگری به عنوان یک AOT(Native) Java image .

ابتدا، متغیرهای محیطی پروژه GCP را تنظیم کنید:

export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)

ساخت ایمیج JIT(JVM):

./mvnw package -Pjvm-image

به گزارش ساخت در ترمینال توجه کنید:

[INFO]     [creator]     Adding layer 'process-types'
[INFO]     [creator]     Adding label 'io.buildpacks.lifecycle.metadata'
[INFO]     [creator]     Adding label 'io.buildpacks.build.metadata'
[INFO]     [creator]     Adding label 'io.buildpacks.project.metadata'
[INFO]     [creator]     Adding label 'org.opencontainers.image.title'
[INFO]     [creator]     Adding label 'org.opencontainers.image.version'
[INFO]     [creator]     Adding label 'org.springframework.boot.version'
[INFO]     [creator]     Setting default process type 'web'
[INFO]     [creator]     Saving docker.io/library/image-analysis-jvm:r17...
[INFO]     [creator]     *** Images (03a44112456e):
[INFO]     [creator]           docker.io/library/image-analysis-jvm:r17
[INFO]     [creator]     Adding cache layer 'paketo-buildpacks/syft:syft'
[INFO]     [creator]     Adding cache layer 'cache.sbom'
[INFO] 
[INFO] Successfully built image 'docker.io/library/image-analysis-jvm:r17'
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  02:11 min
[INFO] Finished at: 2022-09-26T13:09:34-04:00
[INFO] ------------------------------------------------------------------------

ساخت ایمیج AOT(Native):

./mvnw package -Pnative-image

گزارش ساخت را در ترمینال مشاهده کنید، که شامل گزارش‌های ساخت تصویر بومی و فشرده‌سازی تصویر با استفاده از UPX می‌شود.

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

...
[INFO]     [creator]     [2/7] Performing analysis...  [***********]                    (147.6s @ 3.10GB)
[INFO]     [creator]       23,362 (94.34%) of 24,763 classes reachable
[INFO]     [creator]       44,657 (68.67%) of 65,029 fields reachable
[INFO]     [creator]      163,926 (70.76%) of 231,656 methods reachable
[INFO]     [creator]          981 classes, 1,402 fields, and 11,026 methods registered for reflection
[INFO]     [creator]           63 classes,    68 fields, and    55 methods registered for JNI access
[INFO]     [creator]            4 native libraries: dl, pthread, rt, z
[INFO]     [creator]     [3/7] Building universe...                                      (21.1s @ 2.66GB)
[INFO]     [creator]     [4/7] Parsing methods...      [****]                            (13.7s @ 4.16GB)
[INFO]     [creator]     [5/7] Inlining methods...     [***]                              (9.6s @ 4.20GB)
[INFO]     [creator]     [6/7] Compiling methods...    [**********]                     (107.6s @ 3.36GB)
[INFO]     [creator]     [7/7] Creating image...                                         (14.7s @ 4.87GB)
[INFO]     [creator]       62.24MB (51.35%) for code area:   100,499 compilation units
[INFO]     [creator]       51.99MB (42.89%) for image heap:  473,948 objects and 473 resources
[INFO]     [creator]        6.98MB ( 5.76%) for other data
[INFO]     [creator]      121.21MB in total
[INFO]     [creator]     --------------------------------------------------------------------------------
[INFO]     [creator]     Top 10 packages in code area:           Top 10 object types in image heap:
[INFO]     [creator]        2.36MB com.google.protobuf             12.71MB byte[] for code metadata
[INFO]     [creator]        1.90MB i.g.x.s.i.e.e.config.core.v3     7.59MB byte[] for embedded resources
[INFO]     [creator]        1.73MB i.g.x.s.i.e.e.config.route.v3    6.66MB java.lang.Class
[INFO]     [creator]        1.67MB sun.security.ssl                 4.62MB byte[] for java.lang.String
[INFO]     [creator]        1.54MB com.google.cloud.vision.v1       4.39MB java.lang.String
[INFO]     [creator]        1.46MB com.google.firestore.v1          3.66MB byte[] for general heap data
[INFO]     [creator]        1.37MB i.g.x.s.i.e.envoy.api.v2.core    1.96MB c.o.s.c.h.DynamicHubCompanion
[INFO]     [creator]        1.32MB i.g.x.s.i.e.e.api.v2.route       1.80MB byte[] for reflection metadata
[INFO]     [creator]        1.09MB java.util                      910.41KB java.lang.String[]
[INFO]     [creator]        1.08MB com.google.re2j                826.95KB c.o.s.c.h.DynamicHu~onMetadata
[INFO]     [creator]       45.94MB for 776 more packages            6.69MB for 3916 more object types
[INFO]     [creator]     --------------------------------------------------------------------------------
[INFO]     [creator]         20.4s (5.6% of total time) in 81 GCs | Peak RSS: 6.75GB | CPU load: 4.53
[INFO]     [creator]     --------------------------------------------------------------------------------
[INFO]     [creator]     Produced artifacts:
[INFO]     [creator]      /layers/paketo-buildpacks_native-image/native-image/services.ImageAnalysisApplication (executable)
[INFO]     [creator]      /layers/paketo-buildpacks_native-image/native-image/services.ImageAnalysisApplication.build_artifacts.txt (txt)
[INFO]     [creator]     ================================================================================
[INFO]     [creator]     Finished generating '/layers/paketo-buildpacks_native-image/native-image/services.ImageAnalysisApplication' in 5m 59s.
[INFO]     [creator]         Executing upx to compress native image
[INFO]     [creator]                            Ultimate Packer for eXecutables
[INFO]     [creator]                               Copyright (C) 1996 - 2020
[INFO]     [creator]     UPX 3.96        Markus Oberhumer, Laszlo Molnar & John Reiser   Jan 23rd 2020
[INFO]     [creator]     
[INFO]     [creator]             File size         Ratio      Format      Name
[INFO]     [creator]        --------------------   ------   -----------   -----------
 127099880 ->  32416676   25.50%   linux/amd64   services.ImageAnalysisApplication
...
[INFO]     [creator]     ===> EXPORTING
...
[INFO]     [creator]     Adding cache layer 'paketo-buildpacks/native-image:native-image'
[INFO]     [creator]     Adding cache layer 'cache.sbom'
[INFO] 
[INFO] Successfully built image 'docker.io/library/image-analysis-native:r17'
------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  05:28 min
[INFO] Finished at: 2022-09-26T13:19:53-04:00
[INFO] ------------------------------------------------------------------------

تأیید کنید که تصاویر ساخته شده‌اند:

docker images | grep image-analysis

دو تصویر را برچسب‌گذاری کرده و به GCR ارسال کنید:

# JIT(JVM) image
docker tag image-analysis-jvm:r17 gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-jvm:r17
docker push gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-jvm:r17

# AOT(Native) image
docker tag image-analysis-native:r17 gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-native:r17
docker push  gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-native:r17

۱۲. استقرار در Cloud Run

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

شما سرویس را دو بار مستقر خواهید کرد، یک بار با استفاده از تصویر JIT(JVM) و بار دوم با استفاده از تصویر AOT(Native). هر دو استقرار سرویس، تصویر یکسانی را از سطل به صورت موازی، برای اهداف مقایسه، پردازش می‌کنند.

ابتدا، متغیرهای محیطی پروژه GCP را تنظیم کنید:

export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
gcloud config set project ${GOOGLE_CLOUD_PROJECT}
gcloud config set run/region 
gcloud config set run/platform managed
gcloud config set eventarc/location europe-west1

ایمیج JIT(JVM) را مستقر کنید و گزارش استقرار را در کنسول مشاهده کنید:

gcloud run deploy image-analysis-jvm \
     --image gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-jvm:r17 \
     --region europe-west1 \
     --memory 2Gi --allow-unauthenticated

...
Deploying container to Cloud Run service [image-analysis-jvm] in project [...] region [europe-west1]
✓ Deploying... Done.                                                                                                                                                               
  ✓ Creating Revision...                                                                                                                                                           
  ✓ Routing traffic...                                                                                                                                                             
  ✓ Setting IAM Policy...                                                                                                                                                          
Done.                                                                                                                                                                              
Service [image-analysis-jvm] revision [image-analysis-jvm-00009-huc] has been deployed and is serving 100 percent of traffic.
Service URL: https://image-analysis-jvm-...-ew.a.run.app

ایمیج AOT(Native) را مستقر کنید و گزارش استقرار را در کنسول مشاهده کنید:

gcloud run deploy image-analysis-native \
     --image gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-native:r17 \
     --region europe-west1 \
     --memory 2Gi --allow-unauthenticated 
...
Deploying container to Cloud Run service [image-analysis-native] in project [...] region [europe-west1]
✓ Deploying... Done.                                                                                                                                                               
  ✓ Creating Revision...                                                                                                                                                           
  ✓ Routing traffic...                                                                                                                                                             
  ✓ Setting IAM Policy...                                                                                                                                                          
Done.                                                                                                                                                                              
Service [image-analysis-native] revision [image-analysis-native-00005-ben] has been deployed and is serving 100 percent of traffic.
Service URL: https://image-analysis-native-...-ew.a.run.app

۱۳. راه‌اندازی محرک‌های Eventarc

Eventarc یک راهکار استاندارد برای مدیریت جریان تغییرات وضعیت، به نام رویدادها، بین میکروسرویس‌های جدا شده ارائه می‌دهد. هنگامی که Eventarc فعال می‌شود، این رویدادها را از طریق اشتراک‌های Pub/Sub به مقاصد مختلف (در این سند، به Event destinations مراجعه کنید) هدایت می‌کند و در عین حال، تحویل، امنیت، مجوز، قابلیت مشاهده و مدیریت خطا را برای شما مدیریت می‌کند.

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

ابتدا، متغیرهای محیطی پروژه GCP را تنظیم کنید:

export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
gcloud config set project ${GOOGLE_CLOUD_PROJECT}
gcloud config set run/region 
gcloud config set run/platform managed
gcloud config set eventarc/location europe-west1

pubsub.publisher به حساب سرویس ذخیره‌سازی ابری اختصاص دهید:

SERVICE_ACCOUNT="$(gsutil kms serviceaccount -p ${GOOGLE_CLOUD_PROJECT})"

gcloud projects add-iam-policy-binding ${GOOGLE_CLOUD_PROJECT} \
    --member="serviceAccount:${SERVICE_ACCOUNT}" \
    --role='roles/pubsub.publisher'

برای پردازش تصویر، تریگرهای Eventarc را برای هر دو تصویر سرویس JVM(JIT) و AOT(Native) تنظیم کنید:

gcloud eventarc triggers list --location=eu

gcloud eventarc triggers create image-analysis-jvm-trigger \
     --destination-run-service=image-analysis-jvm \
     --destination-run-region=europe-west1 \
     --location=eu \
     --event-filters="type=google.cloud.storage.object.v1.finalized" \
     --event-filters="bucket=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}" \
     --service-account=${PROJECT_NUMBER}-compute@developer.gserviceaccount.com

gcloud eventarc triggers create image-analysis-native-trigger \
     --destination-run-service=image-analysis-native \
     --destination-run-region=europe-west1 \
     --location=eu \
     --event-filters="type=google.cloud.storage.object.v1.finalized" \
     --event-filters="bucket=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}" \
     --service-account=${PROJECT_NUMBER}-compute@developer.gserviceaccount.com    

مشاهده می‌کنید که دو تریگر ایجاد شده‌اند:

gcloud eventarc triggers list --location=eu

۱۴. نسخه‌های سرویس را آزمایش کنید

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

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

ff8a6567afc76235.png

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

برای مثال، یک تصویر GeekHour.jpeg در مسیر /services/image-analysis/java به همراه کدبیس شما ارائه می‌شود. یک تصویر را انتخاب کنید و Open button فشار دهید:

۳۴۷b76e8b775f2f5.png

اکنون می‌توانید اجرای سرویس را بررسی کنید، که با image-analysis-jvm شروع می‌شود و به دنبال آن image-analysis-native قرار دارد.

از منوی «همبرگر» (☰)، به سرویس Cloud Run > image-analysis-jvm بروید.

روی Logs کلیک کنید و خروجی را مشاهده کنید:

810a8684414ceafa.png

و در واقع، در لیست گزارش‌ها، می‌توانم ببینم که سرویس JIT(JVM) به نام image-analysis-jvm فراخوانی شده است.

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

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

شما این فرآیند را برای سرویس image-analysis-native تکرار خواهید کرد.

از منوی «همبرگری» (☰)، به سرویس Cloud Run > image-analysis-native بروید.

روی Logs کلیک کنید و خروجی را مشاهده کنید:

b80308c7d0f55a3.png

اکنون باید بررسی کنید که آیا فراداده‌های تصویر در Fiorestore ذخیره شده‌اند یا خیر.

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

933a20a9709cb006.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) دید ابری
  • فروشگاه ابری فایر استور
  • تصاویر بومی جاوا

مراحل بعدی