תמונה יומית: שיעור Lab 1 – אחסון וניתוח של תמונות

1. סקירה כללית

בשיעור ה-Lab הראשון של כתיבת קוד תעלו תמונות לקטגוריה. הפעולה הזו תיצור אירוע של יצירת קובץ שיטופל על ידי פונקציה. הפונקציה תשלח קריאה ל-Vision API כדי לבצע ניתוח תמונות ולשמור את התוצאות במאגר נתונים.

d650ca5386ea71ad.png

מה תלמדו

  • Cloud Storage
  • Cloud Functions
  • Cloud Vision API
  • Cloud Firestore

2. הגדרה ודרישות

הגדרת סביבה בקצב עצמאי

  1. נכנסים למסוף Google Cloud ויוצרים פרויקט חדש או עושים שימוש חוזר בפרויקט קיים. אם אין לכם עדיין חשבון Gmail או חשבון Google Workspace, עליכם ליצור חשבון.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • Project name הוא השם המוצג של המשתתפים בפרויקט. זו מחרוזת תווים שלא משמשת את Google APIs. אפשר לעדכן אותו בכל שלב.
  • Project ID חייב להיות ייחודי בכל הפרויקטים ב-Google Cloud ואי אפשר לשנות אותו (אי אפשר לשנות אותו אחרי שמגדירים אותו). מסוף Cloud יוצר מחרוזת ייחודית באופן אוטומטי; בדרך כלל לא מעניין אותך מה זה. ברוב ה-Codelabs תצטרכו להפנות אל מזהה הפרויקט (בדרך כלל הוא מזוהה כ-PROJECT_ID). אם המזהה שנוצר לא מוצא חן בעיניך, יש לך אפשרות ליצור מזהה אקראי אחר. לחלופין, אפשר לנסות תבנית משלך ולבדוק אם היא זמינה. לא ניתן לשנות אותו אחרי השלב הזה, והוא יישאר למשך הפרויקט.
  • לידיעתך, יש ערך שלישי – Project Number (מספר פרויקט), שחלק מממשקי ה-API משתמשים בו. מידע נוסף על כל שלושת הערכים האלה זמין במסמכי התיעוד.
  1. בשלב הבא צריך להפעיל את החיוב במסוף Cloud כדי להשתמש במשאבים או בממשקי API של Cloud. מעבר ב-Codelab הזה לא אמור לעלות הרבה, אם בכלל. כדי להשבית את המשאבים ולא לצבור חיובים מעבר למדריך הזה, אתם יכולים למחוק את המשאבים שיצרתם או למחוק את הפרויקט כולו. משתמשים חדשים ב-Google Cloud זכאים להצטרף לתוכנית תקופת ניסיון בחינם בשווי 1,200 ש"ח.

הפעלת Cloud Shell

אומנם אפשר להפעיל את Google Cloud מרחוק מהמחשב הנייד, אבל ב-Codelab הזה משתמשים ב-Google Cloud Shell, סביבת שורת הפקודה שפועלת ב-Cloud.

במסוף Google Cloud, לוחצים על הסמל של Cloud Shell בסרגל הכלים שבפינה השמאלית העליונה:

55efc1aaa7a4d3ad.png

נדרשים רק כמה דקות כדי להקצות את הסביבה ולהתחבר אליה. בסיום התהליך, אתם אמורים לראות משהו כזה:

7ffe5cbb04455448.png

למכונה הווירטואלית הזו נטען כל כלי הפיתוח הדרושים. יש בה ספריית בית בנפח מתמיד של 5GB והיא פועלת ב-Google Cloud, מה שמשפר משמעותית את ביצועי הרשת והאימות. כל העבודה ב-Codelab הזה יכולה להתבצע בתוך דפדפן. אתה לא צריך להתקין שום דבר.

3. הפעלת ממשקי API

בשיעור ה-Lab הזה תשתמשו ב-Cloud Functions וב-Vision API, אבל קודם צריך להפעיל אותם ב-Cloud Console או באמצעות gcloud.

כדי להפעיל את Vision API במסוף Cloud, מחפשים את Cloud Vision API בסרגל החיפוש:

cf48b1747ba6a6fb.png

בשלב הזה מגיעים לדף Cloud Vision API:

ba4af419e6086fbb.png

לוחצים על הלחצן 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.

1930e055d138150a.png

מתן שם לקטגוריה

לוחצים על הלחצן CREATE BUCKET.

34147939358517f8.png

לוחצים על CONTINUE.

בחירת מיקום

197817f20be07678.png

יוצרים קטגוריה מרובת אזורים באזור הרצוי (כאן Europe).

לוחצים על CONTINUE.

בחירת סוג האחסון (storage class) שמוגדר כברירת מחדל

53cd91441c8caf0e.png

צריך לבחור את סוג האחסון (storage class) Standard לנתונים.

לוחצים על CONTINUE.

הגדרה של בקרת גישה

8c2b3b459d934a51.png

מאחר שעובדים עם תמונות הנגישות לציבור, רצוי שלכל התמונות שלנו המאוחסנות בקטגוריה הזו תהיה אותה בקרת גישה אחידה.

צריך לבחור באפשרות Uniform של בקרת הגישה.

לוחצים על CONTINUE.

הגדרת הגנה/הצפנה

d931c24c3e705a68.png

אני רוצה להשאיר את ברירת המחדל (Google-managed key), כי לא ייעשה שימוש במפתחות ההצפנה שלכם).

לוחצים על CREATE כדי לסיים את יצירת הקטגוריה.

הוספת כל המשתמשים כמציג של נפח אחסון

מעבר לכרטיסייה Permissions:

d0ecfdcff730ea51.png

מוסיפים לקטגוריה חבר allUsers עם התפקיד Storage > Storage Object Viewer:

e9f25ec1ea0b6cc6.png

לוחצים על 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:

a98ed4ba17873e40.png

בודקים שניתן להעלות תמונות לקטגוריה ושהתמונות שהועלו יהיו זמינות לציבור, כפי שהוסבר בשלב הקודם.

6. בדיקת הגישה הציבורית לקטגוריה

בחזרה לדפדפן האחסון, הקטגוריה שלך תופיע ברשימה עם הכיתוב 'Public' (ציבורי) גישה (כולל סימן אזהרה שמזכיר שלכל אחד יש גישה לתוכן של הקטגוריה).

89e7a4d2c80a0319.png

הקטגוריה שלכם מוכנה עכשיו לקבל תמונות.

בלחיצה על שם הקטגוריה יוצגו פרטי הקטגוריה.

131387f12d3eb2d3.png

שם תוכלו לנסות את הלחצן Upload files, כדי לבדוק אם אפשר להוסיף תמונה לקטגוריה. חלון קופץ של הכלי לבחירת קבצים יבקש מכם לבחור קובץ. אחרי הבחירה, הקובץ יועלה לקטגוריה, ואז תוצג שוב הגישה מסוג public ששויכו באופן אוטומטי לקובץ החדש.

e87584471a6e9c6d.png

לצד תווית הגישה Public, יופיע גם סמל קישור קטן. כשתלחצו עליו, הדפדפן שלכם ינווט לכתובת ה-URL הציבורית של אותה תמונה, שתופיע בתבנית:

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

כאשר BUCKET_NAME הוא השם הייחודי הגלובלי שבחרתם לקטגוריה, ואז שם הקובץ של התמונה שלכם.

על ידי לחיצה על תיבת הסימון לצד שם התמונה, הלחצן DELETE יופעל, ותהיה לך אפשרות למחוק את התמונה הראשונה.

7. יצירת הפונקציה

בשלב הזה תיצרו פונקציה שמגיבה לאירועים של העלאת תמונות.

נכנסים לקטע Cloud Functions במסוף Google Cloud. אחרי שנכנסים אליו, שירות Cloud Functions יופעל באופן אוטומטי.

9d29e8c026a7a53f.png

לוחצים על Create function.

בוחרים שם (למשל, picture-uploaded) והאזור (חשוב לזכור להיות עקביים עם בחירת האזור לקטגוריה):

4bb222633e6f278.png

יש שני סוגי פונקציות:

  • פונקציות HTTP שאפשר להפעיל דרך כתובת URL (כלומר, Web API),
  • פונקציות ברקע שאירוע מסוים יכול להפעיל.

רוצים ליצור פונקציית רקע שמופעלת כשמעלים קובץ חדש לקטגוריה Cloud Storage:

d9a12fcf58f4813c.png

אתם מתעניינים באירוע Finalize/Create, שהוא האירוע שמופעל כשיוצרים או מעדכנים קובץ בקטגוריה:

b30c8859b07dc4cb.png

עליכם לבחור את הקטגוריה שנוצרה בעבר, כדי שהפונקציות של Cloud Functions יקבלו התראה בכל פעם שיוצרים או מעדכנים קובץ בקטגוריה הספציפית הזו:

cb15a1f4c7a1ca5f.png

לוחצים על Select כדי לבחור את הקטגוריה שיצרתם קודם, ואז לוחצים על Save

c1933777fac32c6a.png

לפני שלוחצים על 'הבא', אפשר להרחיב ולשנות את ברירות המחדל (זיכרון של 256MB) בקטע הגדרות זמן ריצה, build, חיבורים ואבטחה, ולעדכן אותן ל-1GB.

83d757e6c38e10.png

אחרי לחיצה על Next, אפשר לכוונן את זמן הריצה, קוד המקור ונקודת הכניסה.

צריך לשמור את Inline editor לפונקציה הזו:

7dccb5a3fa66363d.png

יש לבחור באחד מזמני הריצה של Node.js:

21defc3b0accd5b4.png

קוד המקור מורכב מקובץ JavaScript מסוג index.js ומקובץ package.json שמספק מטא-נתונים ויחסי תלות שונים.

משאירים את קטע הקוד שמוגדר כברירת מחדל: הוא מתעד את שם הקובץ של התמונה שהועלתה:

465aca96eb8ca5f9.png

בשלב הזה, כדאי לשמור את שם הפונקציה להפעלה ב-helloGCS, למטרות בדיקה.

לוחצים על Deploy כדי ליצור את הפונקציה ולפרוס אותה. אחרי שהפריסה הצליחה, אמור להופיע סימן וי מוקף בעיגול ירוק ברשימת הפונקציות:

e9d78025d16651aa.png

8. בדיקת הפונקציה

בשלב הזה, בודקים שהפונקציה מגיבה לאירועי אחסון.

מתוך "המבורגר" (\t) בתפריט, צריך לנווט חזרה לדף Storage.

לוחצים על קטגוריית התמונות, ואז על Upload files כדי להעלות תמונה.

21767ec3cb8b18de.png

כדי לעבור לדף Logging > Logs Explorer, צריך לעבור שוב במסוף Cloud.

בבורר Log Fields, בוחרים באפשרות Cloud Function כדי לראות את היומנים שמיועדים לפונקציות שלכם. גוללים למטה בין שדות היומן ואפשר אפילו לבחור פונקציה ספציפית כדי לקבל תצוגה מפורטת יותר של היומנים הקשורים לפונקציות. בוחרים את הפונקציה picture-uploaded.

אתם אמורים לראות את פריטי היומן שבהם מוזכרים יצירת הפונקציה, את זמני ההתחלה והסיום של הפונקציה ואת הצהרת היומן בפועל:

e8ba7d39c36df36c.png

בהצהרת היומן שלנו כתוב: Processing file: pic-a-daily-architecture-events.png, כלומר, האירוע שקשור ליצירה ולאחסון של התמונה הזו אכן הופעל כמצופה.

9. הכנת מסד הנתונים

תוכלו לאחסן מידע על התמונה שסופקה על ידי Vision API במסד הנתונים של Cloud Firestore – מסד נתונים מהיר, מנוהל וללא שרת (serverless), שתומך בענן. כדי להכין את מסד הנתונים, עוברים לקטע Firestore במסוף Cloud:

9e4708d2257de058.png

מוצעות שתי אפשרויות: Native mode או Datastore mode. להשתמש במצב המקורי, שמציע תכונות נוספות כמו תמיכה במצב אופליין וסנכרון בזמן אמת.

לוחצים על SELECT NATIVE MODE.

9449ace8cc84de43.png

בוחרים מספר אזורים (כאן באירופה, אבל עדיף לפחות אותו אזור שבו הפונקציה וקטגוריית האחסון שלכם מוגדרות.

לוחצים על הלחצן CREATE DATABASE.

לאחר שמסד הנתונים נוצר, אתם אמורים לראות:

56265949a124819e.png

כדי ליצור אוסף חדש, לוחצים על הלחצן + START COLLECTION.

שם האוסף pictures.

75806ee24c4e13a7.png

לא צריך ליצור מסמך. תוכלו להוסיף אותן באופן פרוגרמטי כי תמונות חדשות יאוחסנו ב-Cloud Storage וינותחו באמצעות Vision API.

לוחצים על Save.

Firestore יוצר מסמך ברירת מחדל ראשון באוסף החדש שנוצר. ניתן למחוק את המסמך בבטחה כי הוא לא מכיל מידע שימושי:

5c2f1e17ea47f48f.png

המסמכים שייווצרו באופן פרוגרמטי באוסף שלנו יכילו 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, בעמודת הניווט שמימין, ולאחר מכן יצירת אינדקס מורכב כמו שמוצג כאן:

ecb8b95e3c791272.png

לוחצים על Create. יצירת האינדקס עשויה להימשך מספר דקות.

10. עדכון הפונקציה

צריך לחזור לדף Functions כדי לעדכן את הפונקציה כדי להפעיל את Vision API לצורך ניתוח התמונות שלנו ואחסון המטא-נתונים ב-Firestore.

מתוך "המבורגר" (\t) בתפריט, עוברים לקטע Cloud Functions, לוחצים על שם הפונקציה, בוחרים בכרטיסייה Source ואז לוחצים על הלחצן EDIT.

קודם כול, עורכים את הקובץ package.json שבו מפורטים יחסי התלות של פונקציית Node.JS. מעדכנים את הקוד כדי להוסיף את התלות של NPM ב-Cloud Vision API:

{
  "name": "picture-analysis-function",
  "version": "0.0.1",
  "dependencies": {
    "@google-cloud/storage": "^1.6.0",
    "@google-cloud/vision": "^1.8.0",
    "@google-cloud/firestore": "^3.4.1"
  }
}

עכשיו, כשיחסי התלות מעודכנים, צריך לעבוד על הקוד של הפונקציה שלנו, על ידי עדכון הקובץ index.js.

מחליפים את הקוד שב-index.js בקוד הבא. מוסבר על כך בשלב הבא.

const vision = require('@google-cloud/vision');
const Storage = require('@google-cloud/storage');
const Firestore = require('@google-cloud/firestore');

const client = new vision.ImageAnnotatorClient();

exports.vision_analysis = async (event, context) => {
    console.log(`Event: ${JSON.stringify(event)}`);

    const filename = event.name;
    const filebucket = event.bucket;

    console.log(`New picture uploaded ${filename} in ${filebucket}`);

    const request = {
        image: { source: { imageUri: `gs://${filebucket}/${filename}` } },
        features: [
            { type: 'LABEL_DETECTION' },
            { type: 'IMAGE_PROPERTIES' },
            { type: 'SAFE_SEARCH_DETECTION' }
        ]
    };

    // invoking the Vision API
    const [response] = await client.annotateImage(request);
    console.log(`Raw vision output for: ${filename}: ${JSON.stringify(response)}`);

    if (response.error === null) {
        // listing the labels found in the picture
        const labels = response.labelAnnotations
            .sort((ann1, ann2) => ann2.score - ann1.score)
            .map(ann => ann.description)
        console.log(`Labels: ${labels.join(', ')}`);

        // retrieving the dominant color of the picture
        const color = response.imagePropertiesAnnotation.dominantColors.colors
            .sort((c1, c2) => c2.score - c1.score)[0].color;
        const colorHex = decColorToHex(color.red, color.green, color.blue);
        console.log(`Colors: ${colorHex}`);

        // determining if the picture is safe to show
        const safeSearch = response.safeSearchAnnotation;
        const isSafe = ["adult", "spoof", "medical", "violence", "racy"].every(k => 
            !['LIKELY', 'VERY_LIKELY'].includes(safeSearch[k]));
        console.log(`Safe? ${isSafe}`);

        // if the picture is safe to display, store it in Firestore
        if (isSafe) {
            const pictureStore = new Firestore().collection('pictures');
            
            const doc = pictureStore.doc(filename);
            await doc.set({
                labels: labels,
                color: colorHex,
                created: Firestore.Timestamp.now()
            }, {merge: true});

            console.log("Stored metadata in Firestore");
        }
    } else {
        throw new Error(`Vision API error: code ${response.error.code}, message: "${response.error.message}"`);
    }
};

function decColorToHex(r, g, b) {
    return '#' + Number(r).toString(16).padStart(2, '0') + 
                 Number(g).toString(16).padStart(2, '0') + 
                 Number(b).toString(16).padStart(2, '0');
}

11. סקירת הפונקציה

בואו נבחן מקרוב את החלקים המעניינים השונים.

קודם כול, אנחנו דורשים את המודולים הנדרשים, עבור Vision, אחסון ו-Firestore:

const vision = require('@google-cloud/vision');
const Storage = require('@google-cloud/storage');
const Firestore = require('@google-cloud/firestore');

לאחר מכן אנחנו מכינים לקוח ל-Vision API:

const client = new vision.ImageAnnotatorClient();

עכשיו מגיע המבנה של הפונקציה שלנו. אנחנו הופכים אותה לפונקציה אסינכרונית, מכיוון שאנחנו משתמשים ביכולות האסינכרוניות / המתנה שמוצגות ב-Node.js 8:

exports.vision_analysis = async (event, context) => {
    ...
    const filename = event.name;
    const filebucket = event.bucket;
    ...
}

שימו לב לחתימה, אבל גם איך אנחנו מאחזרים את שם הקובץ והקטגוריה שהפעילו את הפונקציה של 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:

const request = {
    image: { source: { imageUri: `gs://${filebucket}/${filename}` } },
    features: [
        { type: 'LABEL_DETECTION' },
        { type: 'IMAGE_PROPERTIES' },
        { type: 'SAFE_SEARCH_DETECTION' }
    ]
};

נדרשות 3 יכולות עיקריות של Vision API:

  • זיהוי תוויות: כדי להבין מה מופיע בתמונות האלה
  • מאפייני תמונה: כדי לספק מאפיינים מעניינים של התמונה (אנחנו מעוניינים בצבע הדומיננטי של התמונה).
  • חיפוש בטוח: כדי לדעת אם התמונה בטוחה להצגה (היא לא צריכה להכיל תוכן למבוגרים בלבד / רפואי / נועז / אלים)

בשלב הזה נוכל לבצע את הקריאה ל-Vision API:

const [response] = await client.annotateImage(request);

לידיעתך, כך נראית התשובה מ-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 (response.error === null) {
    ...
} else {
    throw new Error(`Vision API error: code ${response.error.code},  
                     message: "${response.error.message}"`);
}

נקבל את התוויות של הדברים, הקטגוריות או הנושאים שמזוהים בתמונה:

const labels = response.labelAnnotations
    .sort((ann1, ann2) => ann2.score - ann1.score)
    .map(ann => ann.description)

אנחנו ממיינים את התוויות לפי הציון הכי גבוה קודם.

אנחנו מעוניינים לדעת מה הצבע הדומיננטי של התמונה:

const color = response.imagePropertiesAnnotation.dominantColors.colors
    .sort((c1, c2) => c2.score - c1.score)[0].color;
const colorHex = decColorToHex(color.red, color.green, color.blue);

אנחנו שוב ממיינים צבעים לפי ציון ולוקחים את הצבע הראשון.

אנחנו גם משתמשים בפונקציית שירות כדי להפוך את הערכים אדומים / ירוקים / כחולים לקוד צבע הקסדצימלי, שבו נוכל להשתמש בגיליונות סגנונות של CSS.

בואו נבדוק אם התמונה מוצגת ללא חשש:

const safeSearch = response.safeSearchAnnotation;
const isSafe = ["adult", "spoof", "medical", "violence", "racy"]
    .every(k => !['LIKELY', 'VERY_LIKELY'].includes(safeSearch[k]));

אנחנו בודקים את המאפיינים של למבוגרים בלבד, זיופים, רפואה, אלימות או נועזות, כדי לראות אם הם לא סבירים או סבירים מאוד.

אם תוצאת החיפוש הבטוח תקינה, נוכל לאחסן מטא-נתונים ב-Firestore:

if (isSafe) {
    const pictureStore = new Firestore().collection('pictures');
            
    const doc = pictureStore.doc(filename);
    await doc.set({
        labels: labels,
        color: colorHex,
        created: Firestore.Timestamp.now()
    }, {merge: true});
}

12. פריסת הפונקציה

זמן לפריסת הפונקציה.

274c1e2fca6c0bd9.png

לוחצים על הלחצן DEPLOY והגרסה החדשה תיפרס, ותוכלו לראות את ההתקדמות:

4e0ac812a9124e7c.png

13. בדיקה חוזרת של הפונקציה

אחרי שהפונקציה נפרסת בהצלחה, מפרסמים תמונה ב-Cloud Storage, בודקים אם הפונקציה מופעלת, מה מחזיר Vision API ואם המטא-נתונים מאוחסנים ב-Firestore.

חוזרים חזרה אל Cloud Storage ולוחצים על הקטגוריה שיצרנו בתחילת שיעור ה-Lab:

d44c1584122311c7.png

בדף הפרטים של הקטגוריה, לוחצים על הלחצן Upload files כדי להעלות תמונה.

26bb31d35fb6aa3d.png

מתוך "המבורגר" (\t) בתפריט, מנווטים לחוקר של Logging > Logs.

בבורר Log Fields, בוחרים באפשרות Cloud Function כדי לראות את היומנים שמיועדים לפונקציות שלכם. גוללים למטה בין שדות היומן ואפשר אפילו לבחור פונקציה ספציפית כדי לקבל תצוגה מפורטת יותר של היומנים הקשורים לפונקציות. בוחרים את הפונקציה picture-uploaded.

b651dca7e25d5b11.png

ואכן, ברשימת היומנים, אני רואה שהפונקציה שלנו הופעלה:

d22a7f24954e4f63.png

ביומנים מצוין תאריך ההתחלה והסיום של הפעלת הפונקציה. בינתיים, אפשר לראות את היומנים שהכנסנו לפונקציה שלנו עם הצהרות console.log() . אנחנו רואים:

  • פרטי האירוע שהפעיל את הפונקציה שלנו,
  • התוצאות הגולמיות מהקריאה ל-Vision API
  • התוויות שנמצאו בתמונה שהעלינו,
  • את המידע על הצבעים הדומיננטיים
  • אם בטוח להציג את התמונה,
  • בסופו של דבר, המטא-נתונים לגבי התמונה אוחסנו ב-Firestore.

9ff7956a215c15da.png

שוב מהמבורגר (\t) בתפריט, עוברים לקטע 'Firestore'. בקטע המשנה Data (מוצג כברירת מחדל), אמור להופיע האוסף pictures עם מסמך חדש שנוסף לתמונה שהעלית:

a6137ab9687da370.png

14. הסרת המשאבים (אופציונלי)

אם אתם לא מתכוונים להמשיך עם שיעורי ה-Lab האחרים בסדרה, תוכלו לפנות משאבים כדי לחסוך בעלויות ולהיות אזרחי הענן באופן כללי. אפשר למחוק משאבים בנפרד באופן הבא.

מוחקים את הקטגוריה:

gsutil rb gs://${BUCKET_PICTURES}

מוחקים את הפונקציה:

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

מוחקים את האוסף של Firestore על ידי בחירה באפשרות 'מחיקת אוסף' מהאוסף:

410b551c3264f70a.png

לחלופין, אפשר למחוק את הפרויקט כולו:

gcloud projects delete ${GOOGLE_CLOUD_PROJECT} 

15. מעולה!

מעולה! הטמעת בהצלחה את השירות למפתחות הצפנה הראשון של הפרויקט!

אילו נושאים דיברנו?

  • Cloud Storage
  • Cloud Functions
  • Cloud Vision API
  • Cloud Firestore

השלבים הבאים