1. סקירה כללית
בשיעור ה-Lab הראשון של כתיבת קוד תעלו תמונות לקטגוריה. הפעולה הזו תיצור אירוע של יצירת קובץ שיטופל על ידי פונקציה. הפונקציה תשלח קריאה ל-Vision API כדי לבצע ניתוח תמונות ולשמור את התוצאות במאגר נתונים.
מה תלמדו
- Cloud Storage
- Cloud Functions
- Cloud Vision API
- Cloud Firestore
2. הגדרה ודרישות
הגדרת סביבה בקצב עצמאי
- נכנסים למסוף Google Cloud ויוצרים פרויקט חדש או עושים שימוש חוזר בפרויקט קיים. אם אין לכם עדיין חשבון Gmail או חשבון Google Workspace, עליכם ליצור חשבון.
- Project name הוא השם המוצג של המשתתפים בפרויקט. זו מחרוזת תווים שלא משמשת את Google APIs. אפשר לעדכן אותו בכל שלב.
- Project ID חייב להיות ייחודי בכל הפרויקטים ב-Google Cloud ואי אפשר לשנות אותו (אי אפשר לשנות אותו אחרי שמגדירים אותו). מסוף Cloud יוצר מחרוזת ייחודית באופן אוטומטי; בדרך כלל לא מעניין אותך מה זה. ברוב ה-Codelabs תצטרכו להפנות אל מזהה הפרויקט (בדרך כלל הוא מזוהה כ-
PROJECT_ID
). אם המזהה שנוצר לא מוצא חן בעיניך, יש לך אפשרות ליצור מזהה אקראי אחר. לחלופין, אפשר לנסות תבנית משלך ולבדוק אם היא זמינה. לא ניתן לשנות אותו אחרי השלב הזה, והוא יישאר למשך הפרויקט. - לידיעתך, יש ערך שלישי – Project Number (מספר פרויקט), שחלק מממשקי ה-API משתמשים בו. מידע נוסף על כל שלושת הערכים האלה זמין במסמכי התיעוד.
- בשלב הבא צריך להפעיל את החיוב במסוף Cloud כדי להשתמש במשאבים או בממשקי API של Cloud. מעבר ב-Codelab הזה לא אמור לעלות הרבה, אם בכלל. כדי להשבית את המשאבים ולא לצבור חיובים מעבר למדריך הזה, אתם יכולים למחוק את המשאבים שיצרתם או למחוק את הפרויקט כולו. משתמשים חדשים ב-Google Cloud זכאים להצטרף לתוכנית תקופת ניסיון בחינם בשווי 1,200 ש"ח.
הפעלת Cloud Shell
אומנם אפשר להפעיל את Google Cloud מרחוק מהמחשב הנייד, אבל ב-Codelab הזה משתמשים ב-Google Cloud Shell, סביבת שורת הפקודה שפועלת ב-Cloud.
במסוף Google Cloud, לוחצים על הסמל של Cloud Shell בסרגל הכלים שבפינה השמאלית העליונה:
נדרשים רק כמה דקות כדי להקצות את הסביבה ולהתחבר אליה. בסיום התהליך, אתם אמורים לראות משהו כזה:
למכונה הווירטואלית הזו נטען כל כלי הפיתוח הדרושים. יש בה ספריית בית בנפח מתמיד של 5GB והיא פועלת ב-Google Cloud, מה שמשפר משמעותית את ביצועי הרשת והאימות. כל העבודה ב-Codelab הזה יכולה להתבצע בתוך דפדפן. אתה לא צריך להתקין שום דבר.
3. הפעלת ממשקי API
בשיעור ה-Lab הזה תשתמשו ב-Cloud Functions וב-Vision API, אבל קודם צריך להפעיל אותם ב-Cloud Console או באמצעות gcloud
.
כדי להפעיל את Vision API במסוף Cloud, מחפשים את Cloud Vision API
בסרגל החיפוש:
בשלב הזה מגיעים לדף Cloud Vision API:
לוחצים על הלחצן ENABLE
.
לחלופין, אפשר גם להפעיל אותו באמצעות Cloud Shell באמצעות כלי שורת הפקודה של Google Cloud.
בתוך 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. יצירת הקטגוריה (מסוף)
יוצרים קטגוריית אחסון לתמונות. אפשר לעשות את זה דרך מסוף Google Cloud Platform ( console.cloud.google.com) או באמצעות כלי שורת הפקודה gsutil מ-Cloud Shell או מסביבת הפיתוח המקומית.
ניווט לאחסון
מתוך "המבורגר" (\t) בתפריט, צריך לעבור לדף Storage
.
מתן שם לקטגוריה
לוחצים על הלחצן CREATE BUCKET
.
לוחצים על CONTINUE
.
בחירת מיקום
יוצרים קטגוריה מרובת אזורים באזור הרצוי (כאן Europe
).
לוחצים על CONTINUE
.
בחירת סוג האחסון (storage class) שמוגדר כברירת מחדל
צריך לבחור את סוג האחסון (storage class) Standard
לנתונים.
לוחצים על CONTINUE
.
הגדרה של בקרת גישה
מאחר שעובדים עם תמונות הנגישות לציבור, רצוי שלכל התמונות שלנו המאוחסנות בקטגוריה הזו תהיה אותה בקרת גישה אחידה.
צריך לבחור באפשרות Uniform
של בקרת הגישה.
לוחצים על CONTINUE
.
הגדרת הגנה/הצפנה
אני רוצה להשאיר את ברירת המחדל (Google-managed key)
, כי לא ייעשה שימוש במפתחות ההצפנה שלכם).
לוחצים על CREATE
כדי לסיים את יצירת הקטגוריה.
הוספת כל המשתמשים כמציג של נפח אחסון
מעבר לכרטיסייה Permissions
:
מוסיפים לקטגוריה חבר allUsers
עם התפקיד Storage > Storage Object Viewer
:
לוחצים על SAVE
.
5. יצירת הקטגוריה (gsutil)
אפשר גם להשתמש בכלי שורת הפקודה gsutil
ב-Cloud Shell כדי ליצור קטגוריות.
ב-Cloud Shell, מגדירים משתנה לשם הייחודי של הקטגוריה. השדה GOOGLE_CLOUD_PROJECT
כבר מוגדר ב-Cloud Shell למזהה הפרויקט הייחודי שלכם. אפשר להוסיף אותו לשם הקטגוריה.
לדוגמה:
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
:
בודקים שניתן להעלות תמונות לקטגוריה ושהתמונות שהועלו יהיו זמינות לציבור, כפי שהוסבר בשלב הקודם.
6. בדיקת הגישה הציבורית לקטגוריה
בחזרה לדפדפן האחסון, הקטגוריה שלך תופיע ברשימה עם הכיתוב 'Public' (ציבורי) גישה (כולל סימן אזהרה שמזכיר שלכל אחד יש גישה לתוכן של הקטגוריה).
הקטגוריה שלכם מוכנה עכשיו לקבל תמונות.
בלחיצה על שם הקטגוריה יוצגו פרטי הקטגוריה.
שם תוכלו לנסות את הלחצן Upload files
, כדי לבדוק אם אפשר להוסיף תמונה לקטגוריה. חלון קופץ של הכלי לבחירת קבצים יבקש מכם לבחור קובץ. אחרי הבחירה, הקובץ יועלה לקטגוריה, ואז תוצג שוב הגישה מסוג public
ששויכו באופן אוטומטי לקובץ החדש.
לצד תווית הגישה Public
, יופיע גם סמל קישור קטן. כשתלחצו עליו, הדפדפן שלכם ינווט לכתובת ה-URL הציבורית של אותה תמונה, שתופיע בתבנית:
https://storage.googleapis.com/BUCKET_NAME/PICTURE_FILE.png
כאשר BUCKET_NAME
הוא השם הייחודי הגלובלי שבחרתם לקטגוריה, ואז שם הקובץ של התמונה שלכם.
על ידי לחיצה על תיבת הסימון לצד שם התמונה, הלחצן DELETE
יופעל, ותהיה לך אפשרות למחוק את התמונה הראשונה.
7. יצירת הפונקציה
בשלב הזה תיצרו פונקציה שמגיבה לאירועים של העלאת תמונות.
נכנסים לקטע Cloud Functions
במסוף Google Cloud. אחרי שנכנסים אליו, שירות Cloud Functions יופעל באופן אוטומטי.
לוחצים על Create function
.
בוחרים שם (למשל, picture-uploaded
) והאזור (חשוב לזכור להיות עקביים עם בחירת האזור לקטגוריה):
יש שני סוגי פונקציות:
- פונקציות HTTP שאפשר להפעיל דרך כתובת URL (כלומר, Web API),
- פונקציות ברקע שאירוע מסוים יכול להפעיל.
רוצים ליצור פונקציית רקע שמופעלת כשמעלים קובץ חדש לקטגוריה Cloud Storage
:
אתם מתעניינים באירוע Finalize/Create
, שהוא האירוע שמופעל כשיוצרים או מעדכנים קובץ בקטגוריה:
עליכם לבחור את הקטגוריה שנוצרה בעבר, כדי שהפונקציות של Cloud Functions יקבלו התראה בכל פעם שיוצרים או מעדכנים קובץ בקטגוריה הספציפית הזו:
לוחצים על Select
כדי לבחור את הקטגוריה שיצרתם קודם, ואז לוחצים על Save
לפני שלוחצים על 'הבא', אפשר להרחיב ולשנות את ברירות המחדל (זיכרון של 256MB) בקטע הגדרות זמן ריצה, build, חיבורים ואבטחה, ולעדכן אותן ל-1GB.
אחרי לחיצה על Next
, אפשר לכוונן את זמן הריצה, קוד המקור ונקודת הכניסה.
צריך לשמור את Inline editor
לפונקציה הזו:
בוחרים באחד מסביבות זמני הריצה של Java, לדוגמה Java 11:
קוד המקור מורכב מקובץ Java
ומקובץ Maven ל-pom.xml
שמספק מטא-נתונים ויחסי תלות שונים.
משאירים את קטע הקוד שמוגדר כברירת מחדל: הוא מתעד את שם הקובץ של התמונה שהועלתה:
בשלב הזה, כדאי לשמור את שם הפונקציה להפעלה ב-Example
, למטרות בדיקה.
לוחצים על Deploy
כדי ליצור את הפונקציה ולפרוס אותה. אחרי שהפריסה הצליחה, אמור להופיע סימן וי מוקף בעיגול ירוק ברשימת הפונקציות:
8. בדיקת הפונקציה
בשלב הזה, בודקים שהפונקציה מגיבה לאירועי אחסון.
מתוך "המבורגר" (\t) בתפריט, צריך לנווט חזרה לדף Storage
.
לוחצים על קטגוריית התמונות, ואז על Upload files
כדי להעלות תמונה.
כדי לעבור לדף Logging > Logs Explorer
, צריך לעבור שוב במסוף Cloud.
בבורר Log Fields
, בוחרים באפשרות Cloud Function
כדי לראות את היומנים שמיועדים לפונקציות שלכם. גוללים למטה בין שדות היומן ואפשר אפילו לבחור פונקציה ספציפית כדי לקבל תצוגה מפורטת יותר של היומנים הקשורים לפונקציות. בוחרים את הפונקציה picture-uploaded
.
אתם אמורים לראות את פריטי היומן שבהם מוזכרים יצירת הפונקציה, את זמני ההתחלה והסיום של הפונקציה ואת הצהרת היומן בפועל:
בהצהרת היומן שלנו כתוב: Processing file: pic-a-daily-architecture-events.png
, כלומר, האירוע שקשור ליצירה ולאחסון של התמונה הזו אכן הופעל כמצופה.
9. הכנת מסד הנתונים
תוכלו לאחסן מידע על התמונה שסופקה על ידי Vision API במסד הנתונים של Cloud Firestore – מסד נתונים מהיר, מנוהל וללא שרת (serverless), שתומך בענן. כדי להכין את מסד הנתונים, עוברים לקטע Firestore
במסוף Cloud:
מוצעות שתי אפשרויות: Native mode
או Datastore mode
. להשתמש במצב המקורי, שמציע תכונות נוספות כמו תמיכה במצב אופליין וסנכרון בזמן אמת.
לוחצים על SELECT NATIVE MODE
.
בוחרים מספר אזורים (כאן באירופה, אבל עדיף לפחות אותו אזור שבו הפונקציה וקטגוריית האחסון שלכם מוגדרות.
לוחצים על הלחצן CREATE DATABASE
.
לאחר שמסד הנתונים נוצר, אתם אמורים לראות:
כדי ליצור אוסף חדש, לוחצים על הלחצן + START COLLECTION
.
שם האוסף pictures
.
לא צריך ליצור מסמך. תוכלו להוסיף אותן באופן פרוגרמטי כי תמונות חדשות יאוחסנו ב-Cloud Storage וינותחו באמצעות Vision API.
לוחצים על Save
.
Firestore יוצר מסמך ברירת מחדל ראשון באוסף החדש שנוצר. ניתן למחוק את המסמך בבטחה כי הוא לא מכיל מידע שימושי:
המסמכים שייווצרו באופן פרוגרמטי באוסף שלנו יכילו 4 שדות:
- name (מחרוזת): שם הקובץ של התמונה שהועלתה, שהוא גם המפתח של המסמך
- labels (מערך מחרוזות): התוויות של הפריטים המזוהים על ידי Vision API
- color (מחרוזת): קוד הצבע ההקסדצימלי של הצבע הדומיננטי (כלומר, #ab12ef)
- נוצר (תאריך): חותמת הזמן של התקופה שבה אוחסנו המטא-נתונים של התמונה
- 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 על ידי לחיצה על Indexes
, בעמודת הניווט שמימין, ולאחר מכן יצירת אינדקס מורכב כמו שמוצג כאן:
לוחצים על Create
. יצירת האינדקס עשויה להימשך מספר דקות.
10. עדכון הפונקציה
צריך לחזור לדף Functions
כדי לעדכן את הפונקציה כדי להפעיל את Vision API לצורך ניתוח התמונות שלנו ואחסון המטא-נתונים ב-Firestore.
מתוך "המבורגר" (\t) בתפריט, עוברים לקטע Cloud Functions
, לוחצים על שם הפונקציה, בוחרים בכרטיסייה Source
ואז לוחצים על הלחצן EDIT
.
קודם כול, עורכים את הקובץ pom.xml
שבו מפורטים יחסי התלות של פונקציית Java שלנו. מעדכנים את הקוד כדי להוסיף את התלות של Maven ב-Cloud Vision 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 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 מפרסמת Bill-of-Materials(BOM)
כדי למנוע התנגשויות תלות. כשמשתמשים בו, אין צורך לציין אף גרסה עבור ספריות הלקוח הנפרדות של Google.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>libraries-bom</artifactId>
<version>26.1.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
לאחר מכן אנחנו מכינים לקוח ל-Vision API:
...
try (ImageAnnotatorClient vision = ImageAnnotatorClient.create()) {
...
עכשיו מגיע המבנה של הפונקציה שלנו. אנחנו אוספים מהאירוע הנכנס את השדות שמעניינים אותנו וממפים אותם למבנה GCSEvent שאנחנו מגדירים:
...
public class ImageAnalysis implements BackgroundFunction<GCSEvent> {
@Override
public void accept(GCSEvent event, Context context)
throws IOException, InterruptedException,
ExecutionException {
...
public static class GCSEvent {
String bucket;
String name;
}
שימו לב לחתימה, אבל גם איך אנחנו מאחזרים את שם הקובץ והקטגוריה שהפעילו את הפונקציה של Cloud Functions.
לידיעתך, כך נראה המטען הייעודי (payload) של האירוע:
{
"bucket":"uploaded-pictures",
"contentType":"image/png",
"crc32c":"efhgyA==",
"etag":"CKqB956MmucCEAE=",
"generation":"1579795336773802",
"id":"uploaded-pictures/Screenshot.png/1579795336773802",
"kind":"storage#object",
"md5Hash":"PN8Hukfrt6C7IyhZ8d3gfQ==",
"mediaLink":"https://www.googleapis.com/download/storage/v1/b/uploaded-pictures/o/Screenshot.png?generation=1579795336773802&alt=media",
"metageneration":"1",
"name":"Screenshot.png",
"selfLink":"https://www.googleapis.com/storage/v1/b/uploaded-pictures/o/Screenshot.png",
"size":"173557",
"storageClass":"STANDARD",
"timeCreated":"2020-01-23T16:02:16.773Z",
"timeStorageClassUpdated":"2020-01-23T16:02:16.773Z",
"updated":"2020-01-23T16:02:16.773Z"
}
אנחנו מכינים בקשה לשליחה דרך לקוח Vision:
ImageSource imageSource = ImageSource.newBuilder()
.setGcsImageUri("gs://" + bucketName + "/" + fileName)
.build();
Image image = Image.newBuilder()
.setSource(imageSource)
.build();
Feature featureLabel = Feature.newBuilder()
.setType(Type.LABEL_DETECTION)
.build();
Feature featureImageProps = Feature.newBuilder()
.setType(Type.IMAGE_PROPERTIES)
.build();
Feature featureSafeSearch = Feature.newBuilder()
.setType(Type.SAFE_SEARCH_DETECTION)
.build();
AnnotateImageRequest request = AnnotateImageRequest.newBuilder()
.addFeatures(featureLabel)
.addFeatures(featureImageProps)
.addFeatures(featureSafeSearch)
.setImage(image)
.build();
נדרשות 3 יכולות עיקריות של Vision API:
- זיהוי תוויות: כדי להבין מה מופיע בתמונות האלה
- מאפייני תמונה: כדי לספק מאפיינים מעניינים של התמונה (אנחנו מעוניינים בצבע הדומיננטי של התמונה).
- חיפוש בטוח: כדי לדעת אם התמונה בטוחה להצגה (היא לא צריכה להכיל תוכן למבוגרים בלבד / רפואי / נועז / אלים)
בשלב הזה נוכל לבצע את הקריאה ל-Vision API:
...
logger.info("Calling the Vision API...");
BatchAnnotateImagesResponse result =
vision.batchAnnotateImages(requests);
List<AnnotateImageResponse> responses = result.getResponsesList();
...
לידיעתך, כך נראית התשובה מ-Vision API:
{
"faceAnnotations": [],
"landmarkAnnotations": [],
"logoAnnotations": [],
"labelAnnotations": [
{
"locations": [],
"properties": [],
"mid": "/m/01yrx",
"locale": "",
"description": "Cat",
"score": 0.9959855675697327,
"confidence": 0,
"topicality": 0.9959855675697327,
"boundingPoly": null
},
✄ - - - ✄
],
"textAnnotations": [],
"localizedObjectAnnotations": [],
"safeSearchAnnotation": {
"adult": "VERY_UNLIKELY",
"spoof": "UNLIKELY",
"medical": "VERY_UNLIKELY",
"violence": "VERY_UNLIKELY",
"racy": "VERY_UNLIKELY",
"adultConfidence": 0,
"spoofConfidence": 0,
"medicalConfidence": 0,
"violenceConfidence": 0,
"racyConfidence": 0,
"nsfwConfidence": 0
},
"imagePropertiesAnnotation": {
"dominantColors": {
"colors": [
{
"color": {
"red": 203,
"green": 201,
"blue": 201,
"alpha": null
},
"score": 0.4175916016101837,
"pixelFraction": 0.44456374645233154
},
✄ - - - ✄
]
}
},
"error": null,
"cropHintsAnnotation": {
"cropHints": [
{
"boundingPoly": {
"vertices": [
{ "x": 0, "y": 118 },
{ "x": 1177, "y": 118 },
{ "x": 1177, "y": 783 },
{ "x": 0, "y": 783 }
],
"normalizedVertices": []
},
"confidence": 0.41695669293403625,
"importanceFraction": 1
}
]
},
"fullTextAnnotation": null,
"webDetection": null,
"productSearchResults": null,
"context": null
}
אם לא מוחזרת שגיאה, אפשר להמשיך מכאן ולכן יש לנו את ההגדרה הזו אם חוסמים:
AnnotateImageResponse response = responses.get(0);
if (response.hasError()) {
logger.info("Error: " + response.getError().getMessage());
return;
}
נקבל את התוויות של הדברים, הקטגוריות או הנושאים שמזוהים בתמונה:
List<String> labels = response.getLabelAnnotationsList().stream()
.map(annotation -> annotation.getDescription())
.collect(Collectors.toList());
logger.info("Annotations found:");
for (String label: labels) {
logger.info("- " + label);
}
אנחנו מעוניינים לדעת מה הצבע הדומיננטי של התמונה:
String mainColor = "#FFFFFF";
ImageProperties imgProps = response.getImagePropertiesAnnotation();
if (imgProps.hasDominantColors()) {
DominantColorsAnnotation colorsAnn =
imgProps.getDominantColors();
ColorInfo colorInfo = colorsAnn.getColors(0);
mainColor = rgbHex(
colorInfo.getColor().getRed(),
colorInfo.getColor().getGreen(),
colorInfo.getColor().getBlue());
logger.info("Color: " + mainColor);
}
אנחנו גם משתמשים בפונקציית שירות כדי להפוך את הערכים אדומים / ירוקים / כחולים לקוד צבע הקסדצימלי, שבו נוכל להשתמש בגיליונות סגנונות של CSS.
בואו נבדוק אם התמונה מוצגת ללא חשש:
boolean isSafe = false;
if (response.hasSafeSearchAnnotation()) {
SafeSearchAnnotation safeSearch =
response.getSafeSearchAnnotation();
isSafe = Stream.of(
safeSearch.getAdult(), safeSearch.getMedical(), safeSearch.getRacy(),
safeSearch.getSpoof(), safeSearch.getViolence())
.allMatch( likelihood ->
likelihood != Likelihood.LIKELY && likelihood != Likelihood.VERY_LIKELY
);
logger.info("Safe? " + isSafe);
}
אנחנו בודקים את המאפיינים של תוכן למבוגרים בלבד, זיוף, רפואה, אלימות או נועזות כדי לראות אם הם לא סבירים או סבירים מאוד.
אם תוצאת החיפוש הבטוח תקינה, נוכל לאחסן מטא-נתונים ב-Firestore:
if (isSafe) {
FirestoreOptions firestoreOptions = FirestoreOptions.getDefaultInstance();
Firestore pictureStore = firestoreOptions.getService();
DocumentReference doc = pictureStore.collection("pictures").document(fileName);
Map<String, Object> data = new HashMap<>();
data.put("labels", labels);
data.put("color", mainColor);
data.put("created", new Date());
ApiFuture<WriteResult> writeResult = doc.set(data, SetOptions.merge());
logger.info("Picture metadata saved in Firestore at " + writeResult.get().getUpdateTime());
}
12. פריסת הפונקציה
זמן לפריסת הפונקציה.
לוחצים על הלחצן DEPLOY
והגרסה החדשה תיפרס, ותוכלו לראות את ההתקדמות:
13. בדיקה חוזרת של הפונקציה
אחרי שהפונקציה נפרסת בהצלחה, מפרסמים תמונה ב-Cloud Storage, בודקים אם הפונקציה מופעלת, מה מחזיר Vision API ואם המטא-נתונים מאוחסנים ב-Firestore.
חוזרים חזרה אל Cloud Storage
ולוחצים על הקטגוריה שיצרנו בתחילת שיעור ה-Lab:
בדף הפרטים של הקטגוריה, לוחצים על הלחצן Upload files
כדי להעלות תמונה.
מתוך "המבורגר" (\t) בתפריט, מנווטים לחוקר של Logging > Logs
.
בבורר Log Fields
, בוחרים באפשרות Cloud Function
כדי לראות את היומנים שמיועדים לפונקציות שלכם. גוללים למטה בין שדות היומן ואפשר אפילו לבחור פונקציה ספציפית כדי לקבל תצוגה מפורטת יותר של היומנים הקשורים לפונקציות. בוחרים את הפונקציה picture-uploaded
.
ואכן, ברשימת היומנים, אני רואה שהפונקציה שלנו הופעלה:
ביומנים מצוין תאריך ההתחלה והסיום של הפעלת הפונקציה. בינתיים, אפשר לראות את היומנים שהכנסנו לפונקציה שלנו עם הצהרות console.log() . אנחנו רואים:
- פרטי האירוע שהפעיל את הפונקציה שלנו,
- התוצאות הגולמיות מהקריאה ל-Vision API
- התוויות שנמצאו בתמונה שהעלינו,
- את המידע על הצבעים הדומיננטיים
- אם בטוח להציג את התמונה,
- בסופו של דבר, המטא-נתונים לגבי התמונה אוחסנו ב-Firestore.
שוב מהמבורגר (\t) בתפריט, עוברים לקטע 'Firestore
'. בקטע המשנה Data
(מוצג כברירת מחדל), אמור להופיע האוסף pictures
עם מסמך חדש שנוסף לתמונה שהעלית:
14. הסרת המשאבים (אופציונלי)
אם אתם לא מתכוונים להמשיך עם שיעורי ה-Lab האחרים בסדרה, תוכלו לפנות משאבים כדי לחסוך בעלויות ולהיות אזרחי הענן באופן כללי. אפשר למחוק משאבים בנפרד באופן הבא.
מוחקים את הקטגוריה:
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