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

สิ่งที่คุณจะได้เรียนรู้
- Cloud Storage
- Cloud Functions
- 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 Functions ด้วย
gcloud services enable cloudfunctions.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. สร้างฟังก์ชัน
ในขั้นตอนนี้ คุณจะสร้างฟังก์ชันที่ตอบสนองต่อเหตุการณ์การอัปโหลดรูปภาพ
ไปที่ส่วน Cloud Functions ของคอนโซล Google Cloud เมื่อไปที่หน้าดังกล่าว ระบบจะเปิดใช้บริการ Cloud Functions โดยอัตโนมัติ

คลิก Create function
เลือกชื่อ (เช่น picture-uploaded) และภูมิภาค (อย่าลืมเลือกภูมิภาคให้สอดคล้องกับตัวเลือกภูมิภาคสำหรับ Bucket)

ฟังก์ชันมี 2 ประเภท ได้แก่
- ฟังก์ชัน HTTP ที่เรียกใช้ผ่าน URL ได้ (เช่น Web API)
- ฟังก์ชันเบื้องหลังที่ทริกเกอร์ได้ด้วยเหตุการณ์บางอย่าง
คุณต้องการสร้างฟังก์ชันพื้นหลังที่จะทริกเกอร์เมื่อมีการอัปโหลดไฟล์ใหม่ไปยัง Bucket Cloud Storage ของเรา

คุณสนใจFinalize/Createประเภทเหตุการณ์ ซึ่งเป็นเหตุการณ์ที่ทริกเกอร์เมื่อมีการสร้างหรืออัปเดตไฟล์ในที่เก็บข้อมูล

เลือก Bucket ที่สร้างไว้ก่อนหน้านี้ เพื่อแจ้งให้ Cloud Functions ทราบว่าจะได้รับการแจ้งเตือนเมื่อมีการสร้าง / อัปเดตไฟล์ใน Bucket นี้

คลิก Select เพื่อเลือก Bucket ที่คุณสร้างไว้ก่อนหน้านี้ แล้วคลิก Save

ก่อนคลิกถัดไป คุณสามารถขยายและแก้ไขค่าเริ่มต้น (หน่วยความจำ 256 MB) ในส่วนการตั้งค่ารันไทม์ การสร้าง การเชื่อมต่อ และความปลอดภัย แล้วอัปเดตเป็น 1 GB

หลังจากคลิก Next แล้ว คุณจะปรับรันไทม์ ซอร์สโค้ด และจุดแรกเข้าได้
เก็บ Inline editor สำหรับฟังก์ชันนี้ไว้

เลือกรันไทม์ Java อย่างใดอย่างหนึ่ง เช่น Java 11

ซอร์สโค้ดประกอบด้วยไฟล์ Java และไฟล์ pom.xml Maven ที่มีข้อมูลเมตาและการอ้างอิงต่างๆ
ปล่อยให้ข้อมูลโค้ดเริ่มต้นเป็นดังนี้ ซึ่งจะบันทึกชื่อไฟล์ของรูปภาพที่อัปโหลด

ตอนนี้ให้ตั้งชื่อฟังก์ชันที่จะเรียกใช้เป็น Example เพื่อวัตถุประสงค์ในการทดสอบ
คลิก Deploy เพื่อสร้างและติดตั้งใช้งานฟังก์ชัน เมื่อการติดตั้งใช้งานสำเร็จแล้ว คุณจะเห็นเครื่องหมายถูกสีเขียวในวงกลมในรายการฟังก์ชัน

8. ทดสอบฟังก์ชัน
ในขั้นตอนนี้ ให้ทดสอบว่าฟังก์ชันตอบสนองต่อเหตุการณ์พื้นที่เก็บข้อมูลหรือไม่
จากเมนู "แฮมเบอร์เกอร์" (☰) ให้กลับไปที่หน้า Storage
คลิกที่ถังรูปภาพ แล้วคลิก Upload files เพื่ออัปโหลดรูปภาพ

ไปยังส่วนต่างๆ ใน Cloud Console อีกครั้งเพื่อไปที่หน้า Logging > Logs Explorer
ในตัวเลือก Log Fields ให้เลือก Cloud Function เพื่อดูบันทึกที่เกี่ยวข้องกับฟังก์ชันของคุณ เลื่อนลงผ่านช่องบันทึก แล้วคุณยังเลือกฟังก์ชันที่เฉพาะเจาะจงเพื่อดูบันทึกที่เกี่ยวข้องกับฟังก์ชันแบบละเอียดยิ่งขึ้นได้ด้วย เลือกฟังก์ชัน picture-uploaded
คุณควรเห็นรายการบันทึกที่กล่าวถึงการสร้างฟังก์ชัน เวลาเริ่มต้นและเวลาสิ้นสุดของฟังก์ชัน และข้อความบันทึกจริงของเรา

ข้อความบันทึกของเราอ่านได้ว่า Processing file: pic-a-daily-architecture-events.png ซึ่งหมายความว่าเหตุการณ์ที่เกี่ยวข้องกับการสร้างและการจัดเก็บรูปภาพนี้ได้ทริกเกอร์ตามที่คาดไว้
9. เตรียมฐานข้อมูล
คุณจะจัดเก็บข้อมูลเกี่ยวกับรูปภาพที่ 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 การสร้างดัชนีอาจใช้เวลาสักครู่
10. อัปเดตฟังก์ชัน
กลับไปที่หน้า Functions เพื่ออัปเดตฟังก์ชันที่จะเรียกใช้ Vision API เพื่อวิเคราะห์รูปภาพและจัดเก็บข้อมูลเมตาใน Firestore
จากเมนู "แฮมเบอร์เกอร์" (☰) ให้ไปที่ส่วน Cloud Functions คลิกชื่อฟังก์ชัน เลือกแท็บ Source แล้วคลิกปุ่ม EDIT
ก่อนอื่น ให้แก้ไขไฟล์ pom.xml ซึ่งแสดงรายการการอ้างอิงของฟังก์ชัน Java อัปเดตโค้ดเพื่อเพิ่มทรัพยากร Dependency ของ 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;
}
}

11. สำรวจฟังก์ชัน
มาดูรายละเอียดส่วนต่างๆ ที่น่าสนใจกัน
ก่อนอื่น เราจะรวมการอ้างอิงที่เฉพาะเจาะจงในไฟล์ pom.xml ของ Maven Google Java Client Libraries เผยแพร่ Bill-of-Materials(BOM) เพื่อขจัดความขัดแย้งของทรัพยากร Dependency เมื่อใช้แล้ว คุณไม่จำเป็นต้องระบุเวอร์ชันสำหรับไลบรารีไคลเอ็นต์ Google แต่ละรายการ
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>libraries-bom</artifactId>
<version>26.1.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
จากนั้นเราจะเตรียมไคลเอ็นต์สำหรับ Vision API ดังนี้
...
try (ImageAnnotatorClient vision = ImageAnnotatorClient.create()) {
...
ตอนนี้มาถึงโครงสร้างของฟังก์ชันแล้ว เราจะบันทึกฟิลด์ที่เราสนใจจากเหตุการณ์ขาเข้าและแมปฟิลด์เหล่านั้นกับโครงสร้าง GCSEvent ที่เรากำหนด
...
public class ImageAnalysis implements BackgroundFunction<GCSEvent> {
@Override
public void accept(GCSEvent event, Context context)
throws IOException, InterruptedException,
ExecutionException {
...
public static class GCSEvent {
String bucket;
String name;
}
สังเกตลายเซ็น รวมถึงวิธีที่เราดึงชื่อไฟล์และ Bucket ที่ทริกเกอร์ Cloud Function
เพย์โหลดของเหตุการณ์มีลักษณะดังนี้
{
"bucket":"uploaded-pictures",
"contentType":"image/png",
"crc32c":"efhgyA==",
"etag":"CKqB956MmucCEAE=",
"generation":"1579795336773802",
"id":"uploaded-pictures/Screenshot.png/1579795336773802",
"kind":"storage#object",
"md5Hash":"PN8Hukfrt6C7IyhZ8d3gfQ==",
"mediaLink":"https://www.googleapis.com/download/storage/v1/b/uploaded-pictures/o/Screenshot.png?generation=1579795336773802&alt=media",
"metageneration":"1",
"name":"Screenshot.png",
"selfLink":"https://www.googleapis.com/storage/v1/b/uploaded-pictures/o/Screenshot.png",
"size":"173557",
"storageClass":"STANDARD",
"timeCreated":"2020-01-23T16:02:16.773Z",
"timeStorageClassUpdated":"2020-01-23T16:02:16.773Z",
"updated":"2020-01-23T16:02:16.773Z"
}
เราเตรียมคำขอที่จะส่งผ่านไคลเอ็นต์ Vision ดังนี้
ImageSource imageSource = ImageSource.newBuilder()
.setGcsImageUri("gs://" + bucketName + "/" + fileName)
.build();
Image image = Image.newBuilder()
.setSource(imageSource)
.build();
Feature featureLabel = Feature.newBuilder()
.setType(Type.LABEL_DETECTION)
.build();
Feature featureImageProps = Feature.newBuilder()
.setType(Type.IMAGE_PROPERTIES)
.build();
Feature featureSafeSearch = Feature.newBuilder()
.setType(Type.SAFE_SEARCH_DETECTION)
.build();
AnnotateImageRequest request = AnnotateImageRequest.newBuilder()
.addFeatures(featureLabel)
.addFeatures(featureImageProps)
.addFeatures(featureSafeSearch)
.setImage(image)
.build();
เราขอความสามารถหลัก 3 อย่างของ Vision API ดังนี้
- การตรวจหาป้ายกำกับ: เพื่อทำความเข้าใจว่ามีอะไรอยู่ในรูปภาพเหล่านั้น
- พร็อพเพอร์ตี้ของรูปภาพ: เพื่อให้แอตทริบิวต์ที่น่าสนใจของรูปภาพ (เราสนใจสีหลักของรูปภาพ)
- การค้นหาปลอดภัย: เพื่อดูว่ารูปภาพปลอดภัยที่จะแสดงหรือไม่ (ไม่ควรมีเนื้อหาสำหรับผู้ใหญ่ / ทางการแพทย์ / ส่อให้เห็นถึงเรื่องเพศ / รุนแรง)
ตอนนี้เราสามารถเรียกใช้ Vision API ได้แล้ว
...
logger.info("Calling the Vision API...");
BatchAnnotateImagesResponse result =
vision.batchAnnotateImages(requests);
List<AnnotateImageResponse> responses = result.getResponsesList();
...
เพื่อเป็นข้อมูลอ้างอิง การตอบกลับจาก Vision API จะมีลักษณะดังนี้
{
"faceAnnotations": [],
"landmarkAnnotations": [],
"logoAnnotations": [],
"labelAnnotations": [
{
"locations": [],
"properties": [],
"mid": "/m/01yrx",
"locale": "",
"description": "Cat",
"score": 0.9959855675697327,
"confidence": 0,
"topicality": 0.9959855675697327,
"boundingPoly": null
},
✄ - - - ✄
],
"textAnnotations": [],
"localizedObjectAnnotations": [],
"safeSearchAnnotation": {
"adult": "VERY_UNLIKELY",
"spoof": "UNLIKELY",
"medical": "VERY_UNLIKELY",
"violence": "VERY_UNLIKELY",
"racy": "VERY_UNLIKELY",
"adultConfidence": 0,
"spoofConfidence": 0,
"medicalConfidence": 0,
"violenceConfidence": 0,
"racyConfidence": 0,
"nsfwConfidence": 0
},
"imagePropertiesAnnotation": {
"dominantColors": {
"colors": [
{
"color": {
"red": 203,
"green": 201,
"blue": 201,
"alpha": null
},
"score": 0.4175916016101837,
"pixelFraction": 0.44456374645233154
},
✄ - - - ✄
]
}
},
"error": null,
"cropHintsAnnotation": {
"cropHints": [
{
"boundingPoly": {
"vertices": [
{ "x": 0, "y": 118 },
{ "x": 1177, "y": 118 },
{ "x": 1177, "y": 783 },
{ "x": 0, "y": 783 }
],
"normalizedVertices": []
},
"confidence": 0.41695669293403625,
"importanceFraction": 1
}
]
},
"fullTextAnnotation": null,
"webDetection": null,
"productSearchResults": null,
"context": null
}
หากไม่มีข้อผิดพลาดแสดงขึ้น เราจะดำเนินการต่อได้ จึงเป็นเหตุผลที่เรามีบล็อก 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());
}
12. ทำให้ฟังก์ชันใช้งานได้
เวลาในการติดตั้งใช้งานฟังก์ชัน

กดปุ่ม DEPLOY แล้วระบบจะติดตั้งใช้งานเวอร์ชันใหม่ คุณจะเห็นความคืบหน้าดังนี้

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

เมื่ออยู่ในหน้ารายละเอียดที่เก็บข้อมูล ให้คลิกปุ่ม Upload files เพื่ออัปโหลดรูปภาพ

จากเมนู "แฮมเบอร์เกอร์" (☰) ให้ไปที่ Logging > Logs Explorer
ในตัวเลือก Log Fields ให้เลือก Cloud Function เพื่อดูบันทึกที่เกี่ยวข้องกับฟังก์ชันของคุณ เลื่อนลงผ่านช่องบันทึก แล้วคุณยังเลือกฟังก์ชันที่เฉพาะเจาะจงเพื่อดูบันทึกที่เกี่ยวข้องกับฟังก์ชันแบบละเอียดยิ่งขึ้นได้ด้วย เลือกฟังก์ชัน picture-uploaded

และในรายการบันทึก ฉันเห็นว่าฟังก์ชันของเราถูกเรียกใช้แล้ว

โดยบันทึกจะระบุจุดเริ่มต้นและจุดสิ้นสุดของการเรียกใช้ฟังก์ชัน และในระหว่างนั้น เราจะเห็นบันทึกที่เราใส่ไว้ในฟังก์ชันด้วยคำสั่ง console.log() เราเห็นว่า
- รายละเอียดของเหตุการณ์ที่ทริกเกอร์ฟังก์ชันของเรา
- ผลลัพธ์ดิบจากการเรียก Vision API
- ป้ายกำกับที่พบในรูปภาพที่เราอัปโหลด
- ข้อมูลสีที่โดดเด่น
- รูปภาพปลอดภัยที่จะแสดงหรือไม่
- และในที่สุดระบบก็จะจัดเก็บข้อมูลเมตาเกี่ยวกับรูปภาพเหล่านั้นไว้ใน Firestore

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

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

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