1. ภาพรวม
ในโค้ดแล็บแรก คุณจะจัดเก็บรูปภาพไว้ใน Bucket ซึ่งจะสร้างเหตุการณ์การสร้างไฟล์ที่บริการที่ติดตั้งใช้งานใน Cloud Run จะจัดการ บริการจะเรียกใช้ Vision API เพื่อทำการวิเคราะห์รูปภาพและบันทึกผลลัพธ์ใน Datastore

สิ่งที่คุณจะได้เรียนรู้
- Cloud Storage
- Cloud Run
- Cloud Vision API
- Cloud Firestore
2. การตั้งค่าและข้อกำหนด
การตั้งค่าสภาพแวดล้อมแบบเรียนรู้ด้วยตนเอง
- ลงชื่อเข้าใช้ Google Cloud Console แล้วสร้างโปรเจ็กต์ใหม่หรือใช้โปรเจ็กต์ที่มีอยู่ซ้ำ หากยังไม่มีบัญชี Gmail หรือ Google Workspace คุณต้องสร้างบัญชี



- ชื่อโปรเจ็กต์คือชื่อที่แสดงสำหรับผู้เข้าร่วมโปรเจ็กต์นี้ ซึ่งเป็นสตริงอักขระที่ Google APIs ไม่ได้ใช้ คุณอัปเดตได้ทุกเมื่อ
- รหัสโปรเจ็กต์จะไม่ซ้ำกันในโปรเจ็กต์ Google Cloud ทั้งหมดและเปลี่ยนแปลงไม่ได้ (เปลี่ยนไม่ได้หลังจากตั้งค่าแล้ว) Cloud Console จะสร้างสตริงที่ไม่ซ้ำกันโดยอัตโนมัติ ซึ่งโดยปกติแล้วคุณไม่จำเป็นต้องสนใจว่าสตริงนั้นคืออะไร ใน Codelab ส่วนใหญ่ คุณจะต้องอ้างอิงรหัสโปรเจ็กต์ (โดยทั่วไปจะระบุเป็น
PROJECT_ID) หากไม่ชอบรหัสที่สร้างขึ้น คุณอาจสร้างรหัสแบบสุ่มอีกรหัสหนึ่งได้ หรือคุณอาจลองใช้ชื่อของคุณเองและดูว่ามีชื่อนั้นหรือไม่ คุณจะเปลี่ยนแปลงรหัสนี้หลังจากขั้นตอนนี้ไม่ได้ และรหัสจะคงอยู่ตลอดระยะเวลาของโปรเจ็กต์ - โปรดทราบว่ายังมีค่าที่ 3 ซึ่งคือหมายเลขโปรเจ็กต์ที่ API บางตัวใช้ ดูข้อมูลเพิ่มเติมเกี่ยวกับค่าทั้ง 3 นี้ได้ในเอกสารประกอบ
- จากนั้นคุณจะต้องเปิดใช้การเรียกเก็บเงินใน Cloud Console เพื่อใช้ทรัพยากร/API ของ Cloud การทำตาม Codelab นี้จะไม่มีค่าใช้จ่ายมากนัก หรืออาจไม่มีค่าใช้จ่ายเลย หากต้องการปิดทรัพยากรเพื่อหลีกเลี่ยงการเรียกเก็บเงินนอกเหนือจากบทแนะนำนี้ คุณสามารถลบทรัพยากรที่สร้างขึ้นหรือลบโปรเจ็กต์ได้ ผู้ใช้ Google Cloud รายใหม่มีสิทธิ์เข้าร่วมโปรแกรมช่วงทดลองใช้ฟรีมูลค่า$300 USD
เริ่มต้น Cloud Shell
แม้ว่าคุณจะใช้งาน Google Cloud จากระยะไกลจากแล็ปท็อปได้ แต่ใน Codelab นี้คุณจะใช้ Google Cloud Shell ซึ่งเป็นสภาพแวดล้อมบรรทัดคำสั่งที่ทำงานในระบบคลาวด์
จาก Google Cloud Console ให้คลิกไอคอน Cloud Shell ในแถบเครื่องมือด้านขวาบน

การจัดสรรและเชื่อมต่อกับสภาพแวดล้อมจะใช้เวลาเพียงไม่กี่นาที เมื่อเสร็จแล้ว คุณควรเห็นข้อความคล้ายกับตัวอย่างต่อไปนี้

เครื่องเสมือนนี้มาพร้อมเครื่องมือพัฒนาซอฟต์แวร์ทั้งหมดที่คุณต้องการ โดยมีไดเรกทอรีหลักแบบถาวรขนาด 5 GB และทำงานบน Google Cloud ซึ่งช่วยเพิ่มประสิทธิภาพเครือข่ายและการตรวจสอบสิทธิ์ได้อย่างมาก คุณสามารถทำงานทั้งหมดใน Codelab นี้ได้ภายในเบราว์เซอร์ คุณไม่จำเป็นต้องติดตั้งอะไร
3. เปิดใช้ API
สำหรับแล็บนี้ คุณจะใช้ Cloud Functions และ Vision API แต่ก่อนอื่นคุณต้องเปิดใช้ใน Cloud Console หรือด้วย gcloud
หากต้องการเปิดใช้ Vision API ใน Cloud Console ให้ค้นหา Cloud Vision API ในแถบค้นหา

ระบบจะนำคุณไปยังหน้า Cloud Vision API

คลิกปุ่ม ENABLE
หรือจะเปิดใช้ใน Cloud Shell โดยใช้เครื่องมือบรรทัดคำสั่ง gcloud ก็ได้
เรียกใช้คำสั่งต่อไปนี้ใน 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
4. สร้างที่เก็บข้อมูล (คอนโซล)
สร้าง Bucket ของพื้นที่เก็บข้อมูลสำหรับรูปภาพ คุณทำได้จากคอนโซล Google Cloud Platform ( console.cloud.google.com) หรือใช้เครื่องมือบรรทัดคำสั่ง gsutil จาก Cloud Shell หรือสภาพแวดล้อมการพัฒนาในเครื่อง
ไปที่พื้นที่เก็บข้อมูล
จากเมนู "แฮมเบอร์เกอร์" (☰) ให้ไปที่หน้า Storage

ตั้งชื่อที่เก็บข้อมูล
คลิกปุ่ม CREATE BUCKET

คลิก CONTINUE
เลือกตำแหน่ง

สร้าง Bucket แบบหลายภูมิภาคในภูมิภาคที่คุณเลือก (ในที่นี้คือ Europe)
คลิก CONTINUE
เลือกคลาสพื้นที่เก็บข้อมูลเริ่มต้น

เลือกคลาสพื้นที่เก็บข้อมูล Standard สำหรับข้อมูล
คลิก CONTINUE
ตั้งค่าการควบคุมการเข้าถึง

เนื่องจากคุณจะทำงานกับรูปภาพที่เข้าถึงได้แบบสาธารณะ คุณจึงต้องการให้รูปภาพทั้งหมดที่จัดเก็บไว้ใน Bucket นี้มีการควบคุมการเข้าถึงแบบเดียวกัน
เลือกตัวเลือกการควบคุมการเข้าถึง Uniform
คลิก CONTINUE
ตั้งค่าการปกป้อง/การเข้ารหัส

คงค่าเริ่มต้น (Google-managed key)) ไว้ เนื่องจากคุณจะไม่ใช้คีย์การเข้ารหัสของคุณเอง
คลิก CREATE เพื่อสร้าง Bucket ให้เสร็จสมบูรณ์
เพิ่ม allUsers เป็นผู้ดูพื้นที่เก็บข้อมูล
ไปที่แท็บ Permissions โดยทำดังนี้

เพิ่มallUsers สมาชิกไปยังที่เก็บข้อมูลที่มีบทบาทเป็น Storage > Storage Object Viewer ดังนี้

คลิก SAVE
5. สร้างที่เก็บข้อมูล (gsutil)
นอกจากนี้ คุณยังใช้gsutilเครื่องมือบรรทัดคำสั่งใน Cloud Shell เพื่อสร้าง Bucket ได้ด้วย
ใน Cloud Shell ให้ตั้งค่าตัวแปรสำหรับชื่อที่เก็บข้อมูลที่ไม่ซ้ำกัน Cloud Shell มี GOOGLE_CLOUD_PROJECT ตั้งค่าเป็นรหัสโปรเจ็กต์ที่ไม่ซ้ำกันของคุณอยู่แล้ว คุณสามารถต่อท้ายชื่อ Bucket ได้
เช่น
export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}
สร้างโซนมาตรฐานแบบหลายภูมิภาคในยุโรป
gsutil mb -l EU gs://${BUCKET_PICTURES}
ตรวจสอบว่าสิทธิ์เข้าถึงระดับ Bucket เหมือนกัน
gsutil uniformbucketlevelaccess set on gs://${BUCKET_PICTURES}
ทำให้ Bucket เป็นแบบสาธารณะ
gsutil iam ch allUsers:objectViewer gs://${BUCKET_PICTURES}
หากไปที่ส่วน Cloud Storage ของคอนโซล คุณควรมีที่เก็บข้อมูล uploaded-pictures สาธารณะ

ทดสอบว่าคุณอัปโหลดรูปภาพไปยังที่เก็บข้อมูลได้ และรูปภาพที่อัปโหลดพร้อมใช้งานแบบสาธารณะตามที่อธิบายไว้ในขั้นตอนก่อนหน้า
6. ทดสอบการเข้าถึงแบบสาธารณะใน Bucket
เมื่อกลับไปที่เบราว์เซอร์พื้นที่เก็บข้อมูล คุณจะเห็นที่เก็บข้อมูลในรายการที่มีสิทธิ์เข้าถึง "สาธารณะ" (รวมถึงเครื่องหมายเตือนที่แจ้งให้คุณทราบว่าทุกคนมีสิทธิ์เข้าถึงเนื้อหาของที่เก็บข้อมูลนั้น)

ตอนนี้ Bucket พร้อมรับรูปภาพแล้ว
หากคลิกชื่อที่เก็บข้อมูล คุณจะเห็นรายละเอียดที่เก็บข้อมูล

ที่นั่น คุณสามารถลองใช้ปุ่ม Upload files เพื่อทดสอบว่าคุณเพิ่มรูปภาพลงใน Bucket ได้ ป๊อปอัปตัวเลือกไฟล์จะขอให้คุณเลือกไฟล์ เมื่อเลือกแล้ว ระบบจะอัปโหลดไปยังที่เก็บข้อมูลของคุณ และคุณจะเห็นpublicสิทธิ์เข้าถึงที่ระบบกำหนดให้กับไฟล์ใหม่นี้โดยอัตโนมัติอีกครั้ง

นอกจากป้ายกำกับสิทธิ์เข้าถึง Public แล้ว คุณยังจะเห็นไอคอนลิงก์เล็กๆ ด้วย เมื่อคลิกที่รูปภาพ เบราว์เซอร์จะนำคุณไปยัง URL สาธารณะของรูปภาพนั้น ซึ่งจะมีรูปแบบดังนี้
https://storage.googleapis.com/BUCKET_NAME/PICTURE_FILE.png
โดย BUCKET_NAME คือชื่อที่ไม่ซ้ำกันทั่วโลกที่คุณเลือกสำหรับ Bucket และตามด้วยชื่อไฟล์ของรูปภาพ
การคลิกช่องทำเครื่องหมายข้างชื่อรูปภาพจะทำให้ปุ่ม DELETE เปิดใช้ และคุณจะลบรูปภาพแรกนี้ได้
7. เตรียมฐานข้อมูล
คุณจะจัดเก็บข้อมูลเกี่ยวกับรูปภาพที่ Vision API ให้ไว้ในฐานข้อมูล Cloud Firestore ซึ่งเป็นฐานข้อมูลเอกสาร NoSQL ที่ดำเนินการบนระบบคลาวด์แบบ Serverless ที่มีการจัดการครบวงจรและรวดเร็ว เตรียมฐานข้อมูลโดยไปที่ส่วน Firestore ของ Cloud Console

โดยมีตัวเลือก 2 ข้อ ได้แก่ Native mode หรือ Datastore mode ใช้โหมดดั้งเดิมซึ่งมีฟีเจอร์เพิ่มเติม เช่น การรองรับการใช้งานออฟไลน์และการซิงค์ข้อมูลแบบเรียลไทม์
คลิก SELECT NATIVE MODE

เลือกแบบหลายภูมิภาค (ในที่นี้คือยุโรป แต่ควรเลือกภูมิภาคเดียวกับฟังก์ชันและที่เก็บข้อมูลของคุณเป็นอย่างน้อย)
คลิกปุ่ม CREATE DATABASE
เมื่อสร้างฐานข้อมูลแล้ว คุณควรเห็นสิ่งต่อไปนี้

สร้างคอลเล็กชันใหม่โดยคลิกปุ่ม + START COLLECTION
ตั้งชื่อคอลเล็กชัน pictures

คุณไม่จำเป็นต้องสร้างเอกสาร คุณจะเพิ่มรูปภาพเหล่านี้โดยอัตโนมัติเมื่อระบบจัดเก็บรูปภาพใหม่ไว้ใน Cloud Storage และ Vision API วิเคราะห์รูปภาพ
คลิก Save
Firestore จะสร้างเอกสารเริ่มต้นแรกในคอลเล็กชันที่สร้างขึ้นใหม่ คุณสามารถลบเอกสารนั้นได้อย่างปลอดภัยเนื่องจากไม่มีข้อมูลที่เป็นประโยชน์

เอกสารที่จะสร้างแบบเป็นโปรแกรมในคอลเล็กชันของเราจะมี 4 ฟิลด์ ได้แก่
- name (สตริง): ชื่อไฟล์ของรูปภาพที่อัปโหลด ซึ่งเป็นคีย์ของเอกสารด้วย
- ป้ายกำกับ (อาร์เรย์ของสตริง): ป้ายกำกับของรายการที่ Vision API รู้จัก
- color (สตริง): รหัสสีเลขฐานสิบหกของสีหลัก (เช่น #ab12ef)
- created (วันที่): การประทับเวลาเมื่อจัดเก็บข้อมูลเมตาของรูปภาพนี้
- thumbnail (บูลีน): ฟิลด์ที่ไม่บังคับซึ่งจะแสดงและมีค่าเป็นจริงหากมีการสร้างภาพขนาดย่อสำหรับรูปภาพนี้
เนื่องจากเราจะค้นหาใน 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 Console ก็ได้ โดยคลิก Indexes ในคอลัมน์การนำทางทางด้านซ้าย แล้วสร้างดัชนีแบบผสมตามที่แสดงด้านล่าง

คลิก Create การสร้างดัชนีอาจใช้เวลาสักครู่
8. โคลนโค้ด
โคลนโค้ด หากคุณยังไม่ได้ทำในโค้ดแล็บก่อนหน้า
git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop
จากนั้นคุณสามารถไปที่ไดเรกทอรีที่มีบริการเพื่อเริ่มสร้างห้องทดลองได้โดยทำดังนี้
cd serverless-photosharing-workshop/services/image-analysis/java
คุณจะมีเลย์เอาต์ไฟล์ต่อไปนี้สำหรับบริการ

9. สำรวจรหัสบริการ
คุณเริ่มต้นด้วยการดูวิธีเปิดใช้ไลบรารีของไคลเอ็นต์ Java ใน pom.xml โดยใช้ BOM ดังนี้
ก่อนอื่น ให้เปิดไฟล์ pom.xml ซึ่งแสดงรายการทรัพยากร Dependency ของแอป Java โดยเน้นที่การใช้ Vision, Cloud Storage และ Firestore API
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0-M3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>services</groupId>
<artifactId>image-analysis</artifactId>
<version>0.0.1</version>
<name>image-analysis</name>
<description>Spring App for Image Analysis</description>
<properties>
<java.version>17</java.version>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
<spring-cloud.version>2023.0.0-M2</spring-cloud.version>
<testcontainers.version>1.19.1</testcontainers.version>
</properties>
...
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>libraries-bom</artifactId>
<version>26.24.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
—
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-web</artifactId>
</dependency>
<dependency>
<groupId>com.google.cloud.functions</groupId>
<artifactId>functions-framework-api</artifactId>
<version>1.1.0</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>
ฟังก์ชันการทำงานจะได้รับการติดตั้งใช้งานในคลาส EventController ทุกครั้งที่มีการอัปโหลดรูปภาพใหม่ไปยังที่เก็บข้อมูล บริการจะได้รับการแจ้งเตือนให้ประมวลผล ดังนี้
@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);
เราขอความสามารถหลัก 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
}
หากไม่มีข้อผิดพลาดแสดงขึ้น เราจะดำเนินการต่อได้ จึงเป็นเหตุผลที่เรามีบล็อก 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) {
ApiFuture<WriteResult> writeResult =
eventService.storeImage(fileName, labels,
mainColor);
logger.info("Picture metadata saved in Firestore at " +
writeResult.get().getUpdateTime());
}
...
public ApiFuture<WriteResult> storeImage(String fileName,
List<String> labels,
String mainColor) {
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());
return doc.set(data, SetOptions.merge());
}
10. สร้างอิมเมจแอปด้วย GraalVM
ในขั้นตอนนี้ (ไม่บังคับ) คุณจะสร้าง JIT based app image จากนั้นสร้าง Native Java app image โดยใช้ GraalVM
หากต้องการเรียกใช้บิลด์ คุณจะต้องตรวจสอบว่าได้ติดตั้งและกำหนดค่า JDK และตัวสร้าง Native Image ที่เหมาะสมแล้ว โดยมีตัวเลือกหลายอย่างให้เลือก
To start ให้ดาวน์โหลด GraalVM 22.3.x Community Edition แล้วทำตามวิธีการในหน้าการติดตั้ง GraalVM
กระบวนการนี้จะง่ายขึ้นมากด้วยความช่วยเหลือจาก SDKMAN!
หากต้องการติดตั้งการกระจาย JDK ที่เหมาะสมด้วย SDKman ให้เริ่มโดยใช้คำสั่งติดตั้ง
sdk install java 17.0.8-graal
สั่งให้ SDKman ใช้เวอร์ชันนี้สำหรับการสร้างทั้ง JIT และ AOT
sdk use java 17.0.8-graal
ใน Cloudshell คุณสามารถติดตั้ง GraalVM และยูทิลิตี native-image ได้อย่างสะดวกด้วยคำสั่งง่ายๆ ดังนี้
# download GraalVM wget https://download.oracle.com/graalvm/17/latest/graalvm-jdk-17_linux-x64_bin.tar.gz tar -xzf graalvm-jdk-17_linux-x64_bin.tar.gz ls -lart # configure Java 17 and GraalVM for Java 17 # note the name of the latest GraalVM version, as unpacked by the tar command echo Existing JVM: $JAVA_HOME cd graalvm-jdk-17.0.8+9.1 export JAVA_HOME=$PWD cd bin export PATH=$PWD:$PATH echo JAVA HOME: $JAVA_HOME echo PATH: $PATH cd ../.. # validate the version with java -version # observe Java(TM) SE Runtime Environment Oracle GraalVM 17.0.8+9.1 (build 17.0.8+9-LTS-jvmci-23.0-b14) Java HotSpot(TM) 64-Bit Server VM Oracle GraalVM 17.0.8+9.1 (build 17.0.8+9-LTS-jvmci-23.0-b14, mixed mode, sharing)
ก่อนอื่น ให้ตั้งค่าตัวแปรสภาพแวดล้อมของโปรเจ็กต์ GCP ดังนี้
export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
จากนั้นคุณสามารถไปที่ไดเรกทอรีที่มีบริการเพื่อเริ่มสร้างห้องทดลองได้โดยทำดังนี้
cd serverless-photosharing-workshop/services/image-analysis/java
สร้างอิมเมจแอปพลิเคชัน JIT โดยใช้คำสั่งต่อไปนี้
./mvnw package
ดูบันทึกของบิลด์ในเทอร์มินัล
... [INFO] Results: [INFO] [INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] [INFO] --- maven-jar-plugin:3.3.0:jar (default-jar) @ image-analysis --- [INFO] Building jar: /home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis-0.0.1.jar [INFO] [INFO] --- spring-boot-maven-plugin:3.2.0-M3:repackage (repackage) @ image-analysis --- [INFO] Replacing main artifact /home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis-0.0.1.jar with repackaged archive, adding nested dependencies in BOOT-INF/. [INFO] The original artifact has been renamed to /home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis-0.0.1.jar.original [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 15.335 s [INFO] Finished at: 2023-10-10T19:33:25Z [INFO] ------------------------------------------------------------------------
สร้างอิมเมจ Native(ใช้ AOT) โดยใช้คำสั่งต่อไปนี้
./mvnw native:compile -Pnative
ดูบันทึกการสร้างในเทอร์มินัล รวมถึงบันทึกการสร้างอิมเมจเนทีฟ
โปรดทราบว่าการสร้างจะใช้เวลานานกว่าปกติเล็กน้อย ขึ้นอยู่กับเครื่องที่คุณใช้ทดสอบ
...
[2/7] Performing analysis... [*********] (124.5s @ 4.53GB)
29,732 (93.19%) of 31,905 classes reachable
60,161 (70.30%) of 85,577 fields reachable
261,973 (67.29%) of 389,319 methods reachable
2,940 classes, 2,297 fields, and 97,421 methods registered for reflection
81 classes, 90 fields, and 62 methods registered for JNI access
4 native libraries: dl, pthread, rt, z
[3/7] Building universe... (11.7s @ 4.67GB)
[4/7] Parsing methods... [***] (6.1s @ 5.91GB)
[5/7] Inlining methods... [****] (4.5s @ 4.39GB)
[6/7] Compiling methods... [******] (35.3s @ 4.60GB)
[7/7] Creating image... (12.9s @ 4.61GB)
80.08MB (47.43%) for code area: 190,483 compilation units
73.81MB (43.72%) for image heap: 660,125 objects and 189 resources
14.95MB ( 8.86%) for other data
168.84MB in total
------------------------------------------------------------------------------------------------------------------------
Top 10 packages in code area: Top 10 object types in image heap:
2.66MB com.google.cloud.vision.v1p4beta1 18.51MB byte[] for code metadata
2.60MB com.google.cloud.vision.v1 9.27MB java.lang.Class
2.49MB com.google.protobuf 7.34MB byte[] for reflection metadata
2.40MB com.google.cloud.vision.v1p3beta1 6.35MB byte[] for java.lang.String
2.17MB com.google.storage.v2 5.72MB java.lang.String
2.12MB com.google.firestore.v1 4.46MB byte[] for embedded resources
1.64MB sun.security.ssl 4.30MB c.oracle.svm.core.reflect.SubstrateMethodAccessor
1.51MB i.g.xds.shaded.io.envoyproxy.envoy.config.core.v3 4.27MB byte[] for general heap data
1.47MB com.google.cloud.vision.v1p2beta1 2.50MB com.oracle.svm.core.hub.DynamicHubCompanion
1.34MB i.g.x.shaded.io.envoyproxy.envoy.config.route.v3 1.17MB java.lang.Object[]
58.34MB for 977 more packages 9.19MB for 4667 more object types
------------------------------------------------------------------------------------------------------------------------
13.5s (5.7% of total time) in 75 GCs | Peak RSS: 9.44GB | CPU load: 6.13
------------------------------------------------------------------------------------------------------------------------
Produced artifacts:
/home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis (executable)
/home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis.build_artifacts.txt (txt)
========================================================================================================================
Finished generating '/home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis' in 3m 57s.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 04:28 min
[INFO] Finished at: 2023-10-10T19:53:30Z
[INFO] ------------------------------------------------------------------------
11. สร้างและเผยแพร่อิมเมจคอนเทนเนอร์
มาสร้างอิมเมจคอนเทนเนอร์ใน 2 เวอร์ชันที่แตกต่างกัน โดยเวอร์ชันหนึ่งเป็น JIT image และอีกเวอร์ชันเป็น Native Java image
ก่อนอื่น ให้ตั้งค่าตัวแปรสภาพแวดล้อมของโปรเจ็กต์ GCP ดังนี้
export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
สร้างอิมเมจ JIT โดยใช้คำสั่งต่อไปนี้
./mvnw spring-boot:build-image -Pji
ดูบันทึกของบิลด์ในเทอร์มินัล
[INFO] [creator] Timer: Saving docker.io/library/image-analysis-maven-jit:latest... started at 2023-10-10T20:00:31Z [INFO] [creator] *** Images (4c84122a1826): [INFO] [creator] docker.io/library/image-analysis-maven-jit:latest [INFO] [creator] Timer: Saving docker.io/library/image-analysis-maven-jit:latest... ran for 6.975913605s and ended at 2023-10-10T20:00:38Z [INFO] [creator] Timer: Exporter ran for 8.068588001s and ended at 2023-10-10T20:00:38Z [INFO] [creator] Timer: Cache started at 2023-10-10T20:00:38Z [INFO] [creator] Reusing cache layer 'paketo-buildpacks/syft:syft' [INFO] [creator] Adding cache layer 'buildpacksio/lifecycle:cache.sbom' [INFO] [creator] Timer: Cache ran for 200.449002ms and ended at 2023-10-10T20:00:38Z [INFO] [INFO] Successfully built image 'docker.io/library/image-analysis-maven-jit:latest' [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 43.887 s [INFO] Finished at: 2023-10-10T20:00:39Z [INFO] ------------------------------------------------------------------------
สร้างอิมเมจ AOT(เนทีฟ) โดยใช้คำสั่งต่อไปนี้
./mvnw spring-boot:build-image -Pnative
ดูบันทึกการสร้างในเทอร์มินัล รวมถึงบันทึกการสร้างอิมเมจเนทีฟ
หมายเหตุ:
- การสร้างจะใช้เวลานานกว่าปกติเล็กน้อย ทั้งนี้ขึ้นอยู่กับเครื่องที่คุณใช้ทดสอบ
- รูปภาพสามารถบีบอัดเพิ่มเติมด้วย UPX ได้ แต่จะส่งผลเสียเล็กน้อยต่อประสิทธิภาพการเริ่มต้น ดังนั้นบิลด์นี้จึงไม่ได้ใช้ UPX ซึ่งเป็นข้อแลกเปลี่ยนเล็กๆ น้อยๆ เสมอ
... [INFO] [creator] Saving docker.io/library/image-analysis-maven-native:latest... [INFO] [creator] *** Images (13167702674e): [INFO] [creator] docker.io/library/image-analysis-maven-native:latest [INFO] [creator] Adding cache layer 'paketo-buildpacks/bellsoft-liberica:native-image-svm' [INFO] [creator] Adding cache layer 'paketo-buildpacks/syft:syft' [INFO] [creator] Adding cache layer 'paketo-buildpacks/native-image:native-image' [INFO] [creator] Adding cache layer 'buildpacksio/lifecycle:cache.sbom' [INFO] [INFO] Successfully built image 'docker.io/library/image-analysis-maven-native:latest' [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 03:37 min [INFO] Finished at: 2023-10-10T20:05:16Z [INFO] ------------------------------------------------------------------------
ตรวจสอบว่าสร้างอิมเมจแล้ว
docker images | grep image-analysis
ติดแท็กและพุชรูปภาพทั้ง 2 ไปยัง GCR
# JIT image
docker tag image-analysis-maven-jit gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-jit
docker push gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-jit
# Native(AOT) image
docker tag image-analysis-maven-native gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-native
docker push gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-native
12. ทำให้ใช้งานได้กับ Cloud Run
ถึงเวลาติดตั้งใช้งานบริการแล้ว
คุณจะติดตั้งใช้งานบริการ 2 ครั้ง โดยครั้งแรกใช้รูปภาพ JIT และครั้งที่ 2 ใช้รูปภาพ AOT(เนทีฟ) การติดตั้งใช้งานบริการทั้ง 2 รายการจะประมวลผลรูปภาพเดียวกันจากที่เก็บข้อมูลแบบขนานกันเพื่อวัตถุประสงค์ในการเปรียบเทียบ
ก่อนอื่น ให้ตั้งค่าตัวแปรสภาพแวดล้อมของโปรเจ็กต์ 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 และดูบันทึกการติดตั้งใช้งานในคอนโซล
gcloud run deploy image-analysis-jit \
--image gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-jit \
--region europe-west1 \
--memory 2Gi --allow-unauthenticated
...
Deploying container to Cloud Run service [image-analysis-jit] in project [...] region [europe-west1]
✓ Deploying... Done.
✓ Creating Revision...
✓ Routing traffic...
✓ Setting IAM Policy...
Done.
Service [image-analysis-jit] revision [image-analysis-jvm-00009-huc] has been deployed and is serving 100 percent of traffic.
Service URL: https://image-analysis-jit-...-ew.a.run.app
ติดตั้งใช้งานอิมเมจเนทีฟและดูบันทึกการติดตั้งใช้งานในคอนโซล
gcloud run deploy image-analysis-native \
--image gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-native \
--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
13. ตั้งค่าทริกเกอร์ Eventarc
Eventarc มีโซลูชันที่ได้มาตรฐานในการจัดการโฟลว์ของการเปลี่ยนแปลงสถานะที่เรียกว่าเหตุการณ์ระหว่างไมโครเซอร์วิสที่แยกออกจากกัน เมื่อมีการทริกเกอร์ Eventarc จะกำหนดเส้นทางเหตุการณ์เหล่านี้ผ่านการสมัครใช้บริการ Pub/Sub ไปยังปลายทางต่างๆ (ในเอกสารนี้ โปรดดูปลายทางของเหตุการณ์) พร้อมทั้งจัดการการนำส่ง ความปลอดภัย การให้สิทธิ์ ความสามารถในการสังเกต และการจัดการข้อผิดพลาดให้คุณ
คุณสามารถสร้างทริกเกอร์ 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 แก่บัญชีบริการ Cloud Storage โดยทำดังนี้
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 สำหรับทั้งอิมเมจบริการ JIT และเนทีฟเพื่อประมวลผลรูปภาพ
gcloud eventarc triggers list --location=eu
gcloud eventarc triggers create image-analysis-jit-trigger \
--destination-run-service=image-analysis-jit \
--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
สังเกตว่ามีการสร้างทริกเกอร์ 2 รายการดังนี้
gcloud eventarc triggers list --location=eu
14. ทดสอบเวอร์ชันของบริการ
เมื่อการติดตั้งใช้งานบริการสำเร็จแล้ว คุณจะโพสต์รูปภาพไปยัง Cloud Storage ดูว่าบริการของเราถูกเรียกใช้หรือไม่ Vision API แสดงผลอะไร และมีการจัดเก็บข้อมูลเมตาใน Firestore หรือไม่
กลับไปที่ Cloud Storage แล้วคลิกที่ Bucket ที่เราสร้างไว้ตอนต้นของแล็บ

เมื่ออยู่ในหน้ารายละเอียดที่เก็บข้อมูล ให้คลิกปุ่ม Upload files เพื่ออัปโหลดรูปภาพ
เช่น มีรูปภาพ GeekHour.jpeg มาพร้อมกับโค้ดเบสของคุณในส่วน /services/image-analysis/java เลือกรูปภาพแล้วกด Open button

ตอนนี้คุณสามารถตรวจสอบการดำเนินการของบริการได้โดยเริ่มจาก image-analysis-jit ตามด้วย image-analysis-native
จากเมนู "แฮมเบอร์เกอร์" (☰) ให้ไปที่บริการ Cloud Run > image-analysis-jit
คลิกบันทึกและสังเกตเอาต์พุต

และในรายการบันทึก ฉันเห็นว่ามีการเรียกใช้บริการ JIT image-analysis-jit
โดยบันทึกจะระบุจุดเริ่มต้นและจุดสิ้นสุดของการดำเนินการบริการ และในระหว่างนั้น เราจะเห็นบันทึกที่เราใส่ไว้ในฟังก์ชันพร้อมกับคำสั่งบันทึกที่ระดับ INFO เราเห็นว่า
- รายละเอียดของเหตุการณ์ที่ทริกเกอร์ฟังก์ชันของเรา
- ผลลัพธ์ดิบจากการเรียก Vision API
- ป้ายกำกับที่พบในรูปภาพที่เราอัปโหลด
- ข้อมูลสีที่โดดเด่น
- รูปภาพปลอดภัยที่จะแสดงหรือไม่
- และในที่สุดระบบก็จะจัดเก็บข้อมูลเมตาเกี่ยวกับรูปภาพเหล่านั้นไว้ใน Firestore
คุณจะต้องทำกระบวนการนี้ซ้ำสำหรับบริการ image-analysis-native
จากเมนู "แฮมเบอร์เกอร์" (☰) ให้ไปที่บริการ Cloud Run > image-analysis-native
คลิกบันทึกและสังเกตเอาต์พุต

คุณจะต้องสังเกตว่าระบบได้จัดเก็บข้อมูลเมตาของรูปภาพไว้ใน Fiorestore หรือไม่
จากเมนู "แฮมเบอร์เกอร์" (☰) อีกครั้ง ให้ไปที่ส่วน Firestore ในDataส่วนย่อย (แสดงโดยค่าเริ่มต้น) คุณควรเห็นคอลเล็กชัน pictures ที่มีเอกสารใหม่เพิ่มเข้ามา ซึ่งสอดคล้องกับรูปภาพที่คุณเพิ่งอัปโหลด

15. ล้างข้อมูล (ไม่บังคับ)
หากไม่ต้องการทำแล็บอื่นๆ ในชุดนี้ต่อ คุณสามารถล้างข้อมูลทรัพยากรเพื่อประหยัดค่าใช้จ่ายและเป็นพลเมืองคลาวด์ที่ดีโดยรวม คุณล้างข้อมูลทรัพยากรแต่ละรายการได้โดยทำดังนี้
ลบ Bucket
gsutil rb gs://${BUCKET_PICTURES}
ลบฟังก์ชันโดยทำดังนี้
gcloud functions delete picture-uploaded --region europe-west1 -q
ลบคอลเล็กชัน Firestore โดยเลือก "ลบคอลเล็กชัน" จากคอลเล็กชัน

หรือจะลบทั้งโปรเจ็กต์ก็ได้โดยทำดังนี้
gcloud projects delete ${GOOGLE_CLOUD_PROJECT}
16. ยินดีด้วย
ยินดีด้วย คุณติดตั้งใช้งานบริการจัดการคีย์แรกของโปรเจ็กต์เรียบร้อยแล้ว
สิ่งที่เราได้พูดถึง
- Cloud Storage
- Cloud Run
- Cloud Vision API
- Cloud Firestore
- รูปภาพ Java ดั้งเดิม