الصورة اليومية: التمرين المعملي 1: تخزين الصور وتحليلها

1. نظرة عامة

في تجربة الترميز الأولى، ستحمّل صورًا في حزمة. سيؤدي ذلك إلى إنشاء حدث إنشاء ملف ستتعامل معه إحدى الدوال. ستطلب الدالة Vision API لإجراء تحليل الصور وحفظ النتائج في مخزن بيانات.

d650ca5386ea71ad.png

أهداف الدورة التعليمية

  • Cloud Storage
  • وظائف السحابة الإلكترونية
  • Cloud Vision API
  • Cloud Firestore

2. الإعداد والمتطلبات

إعداد البيئة بالسرعة التي تناسبك

  1. سجِّل الدخول إلى Google Cloud Console وأنشِئ مشروعًا جديدًا أو أعِد استخدام مشروع حالي. إذا لم يكن لديك حساب على Gmail أو Google Workspace، عليك إنشاء حساب.

b35bf95b8bf3d5d8.pnga99b7ace416376c4.pngbd84a6d3004737c5.png

  • اسم المشروع هو الاسم المعروض للمشاركين في هذا المشروع. وهي سلسلة أحرف لا تستخدمها Google APIs. ويمكنك تعديله في أي وقت.
  • يجب أن يكون رقم تعريف المشروع فريدًا في جميع مشاريع Google Cloud، كما أنّه غير قابل للتغيير (لا يمكن تغييره بعد ضبطه). تنشئ Cloud Console سلسلة فريدة تلقائيًا، ولا يهمّك عادةً ما هي. في معظم دروس البرمجة، عليك الرجوع إلى رقم تعريف المشروع (يتم تحديده عادةً على أنّه PROJECT_ID). إذا لم يعجبك رقم التعريف الذي تم إنشاؤه، يمكنك إنشاء رقم تعريف عشوائي آخر. يمكنك بدلاً من ذلك تجربة اسم مستخدم من اختيارك ومعرفة ما إذا كان متاحًا. ولا يمكن تغييرها بعد هذه الخطوة وستبقى سارية طوال مدة المشروع.
  • للعلم، هناك قيمة ثالثة، وهي رقم المشروع الذي تستخدمه بعض واجهات برمجة التطبيقات. يمكنك الاطّلاع على مزيد من المعلومات حول هذه القيم الثلاث في المستندات.
  1. بعد ذلك، عليك تفعيل الفوترة في Cloud Console لاستخدام موارد/واجهات برمجة تطبيقات Cloud. لن تكلفك تجربة هذا الدرس البرمجي الكثير من المال، إن وُجدت تكلفة على الإطلاق. لإيقاف الموارد كي لا يتم تحصيل رسوم منك بعد هذا البرنامج التعليمي، يمكنك حذف الموارد التي أنشأتها أو حذف المشروع بأكمله. يمكن لمستخدمي Google Cloud الجدد الاستفادة من برنامج الفترة التجريبية المجانية بقيمة 300 دولار أمريكي.

بدء Cloud Shell

على الرغم من إمكانية تشغيل Google Cloud عن بُعد من الكمبيوتر المحمول، ستستخدم في هذا الدرس التطبيقي حول الترميز Google Cloud Shell، وهي بيئة سطر أوامر تعمل في السحابة الإلكترونية.

من Google Cloud Console، انقر على رمز Cloud Shell في شريط الأدوات أعلى يسار الصفحة:

55efc1aaa7a4d3ad.png

لن يستغرق توفير البيئة والاتصال بها سوى بضع لحظات. عند الانتهاء، من المفترض أن يظهر لك ما يلي:

7ffe5cbb04455448.png

يتم تحميل هذه الآلة الافتراضية مزوّدة بكل أدوات التطوير التي ستحتاج إليها. توفّر هذه الخدمة دليلًا منزليًا دائمًا بسعة 5 غيغابايت، وتعمل على Google Cloud، ما يؤدي إلى تحسين أداء الشبكة والمصادقة بشكل كبير. يمكن إكمال جميع المهام في هذا الدرس العملي ضمن المتصفّح. لست بحاجة إلى تثبيت أي تطبيق.

3- تفعيل واجهات برمجة التطبيقات

في هذا المختبر، ستستخدم Cloud Functions وVision API، ولكن يجب أولاً تفعيلهما إما في Cloud Console أو باستخدام gcloud.

لتفعيل Vision API في Cloud Console، ابحث عن Cloud Vision API في شريط البحث:

cf48b1747ba6a6fb.png

ستنتقل إلى صفحة Cloud Vision API:

ba4af419e6086fbb.png

انقر على الزر 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. إنشاء الحزمة (وحدة التحكّم)

أنشئ حزمة تخزين للصور. يمكنك إجراء ذلك من خلال وحدة تحكّم Google Cloud Platform ( console.cloud.google.com) أو باستخدام أداة سطر الأوامر gsutil من Cloud Shell أو بيئة التطوير المحلية.

من قائمة الخطوط الثلاثة (☰)، انتقِل إلى صفحة Storage.

1930e055d138150a.png

تسمية الحزمة

انقر على الزر CREATE BUCKET.

34147939358517f8.png

انقر على CONTINUE.

اختيار الموقع الجغرافي

197817f20be07678.png

أنشئ حزمة متعددة المناطق في المنطقة التي تختارها (Europe هنا).

انقر على CONTINUE.

اختيار فئة التخزين التلقائية

53cd91441c8caf0e.png

اختَر Standard فئة التخزين لبياناتك.

انقر على CONTINUE.

ضبط عناصر التحكّم في إمكانية الوصول

8c2b3b459d934a51.png

بما أنّك ستعمل على صور متاحة للجميع، عليك التأكّد من أنّ جميع الصور المخزّنة في هذا الحزمة تتضمّن عناصر التحكّم نفسها في الوصول الموحّد.

اختَر خيار التحكّم في الوصول Uniform.

انقر على CONTINUE.

ضبط الحماية/التشفير

d931c24c3e705a68.png

احتفِظ بالإعداد التلقائي (Google-managed key))، لأنّك لن تستخدم مفاتيح التشفير الخاصة بك.

انقر على CREATE لإنهاء عملية إنشاء الحزمة.

إضافة allUsers كمشاهد لمساحة التخزين

انتقِل إلى علامة التبويب 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. اختبار إمكانية الوصول العلني إلى الحزمة

بالرجوع إلى متصفّح مساحة التخزين، سيظهر الحزمة في القائمة مع إذن الوصول "عام" (بما في ذلك علامة تحذير تذكّرك بأنّ أي مستخدم يمكنه الوصول إلى محتوى هذه الحزمة).

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 Console. عند الانتقال إلى هذه الصفحة، سيتم تفعيل خدمة Cloud Functions تلقائيًا.

9d29e8c026a7a53f.png

انقر على Create function.

اختَر اسمًا (مثلاً picture-uploaded) والمنطقة (تذكَّر أن تكون متسقًا مع اختيار المنطقة للحزمة):

4bb222633e6f278.png

هناك نوعان من الدوال:

  • وظائف HTTP التي يمكن استدعاؤها من خلال عنوان URL (أي واجهة برمجة تطبيقات ويب)
  • وظائف الخلفية التي يمكن تشغيلها بواسطة حدث معيّن

تريد إنشاء دالة تعمل في الخلفية يتم تشغيلها عند تحميل ملف جديد إلى الحزمة Cloud Storage:

d9a12fcf58f4813c.png

أنت مهتم بنوع الحدث Finalize/Create، وهو الحدث الذي يتم تشغيله عند إنشاء ملف أو تعديله في الحزمة:

b30c8859b07dc4cb.png

اختَر الحزمة التي تم إنشاؤها من قبل لإعلام Cloud Functions عند إنشاء ملف أو تعديله في هذه الحزمة تحديدًا:

cb15a1f4c7a1ca5f.png

انقر على Select لاختيار الحزمة التي أنشأتها سابقًا، ثم انقر على Save.

c1933777fac32c6a.png

قبل النقر على "التالي"، يمكنك توسيع الإعدادات التلقائية (ذاكرة بسعة 256 ميغابايت) وتعديلها ضمن إعدادات وقت التشغيل والإنشاء والاتصالات والأمان وتعديلها إلى 1 غيغابايت.

83d757e6c38e10.png

بعد النقر على Next، يمكنك ضبط وقت التشغيل ورمز المصدر ونقطة الدخول.

احتفِظ بالرمز Inline editor لهذه الدالة:

7dccb5a3fa66363d.png

اختَر أحد أوقات تشغيل Node.js:

21defc3b0accd5b4.png

يتألف رمز المصدر من ملف index.js JavaScript وملف package.json يوفّر بيانات وصفية وتبعيات مختلفة.

اترك مقتطف الرمز التلقائي: يسجّل اسم ملف الصورة التي تم تحميلها:

465aca96eb8ca5f9.png

في الوقت الحالي، احتفِظ باسم الدالة المطلوب تنفيذها على النحو التالي: helloGCS، وذلك لأغراض الاختبار.

انقر على Deploy لإنشاء الدالة ونشرها. بعد اكتمال عملية النشر بنجاح، من المفترض أن تظهر لك علامة اختيار داخل دائرة خضراء في قائمة الدوال:

e9d78025d16651aa.png

8. اختبار الدالة

في هذه الخطوة، اختبِر ما إذا كانت الدالة تستجيب لأحداث التخزين.

من قائمة الخطوط الثلاثة (☰)، ارجع إلى صفحة Storage.

انقر على حزمة الصور، ثم على Upload files لتحميل صورة.

21767ec3cb8b18de.png

انتقِل مرة أخرى داخل وحدة تحكّم السحابة الإلكترونية إلى صفحة Logging > Logs Explorer.

في أداة الاختيار Log Fields، اختَر Cloud Function للاطّلاع على السجلات المخصّصة لوظائفك. انتقِل للأسفل خلال "حقول السجلّ"، ويمكنك حتى اختيار دالة معيّنة للحصول على عرض أكثر تفصيلاً لسجلات الدوال ذات الصلة. اختَر الدالة picture-uploaded.

من المفترض أن تظهر لك إدخالات السجلّ التي تشير إلى إنشاء الدالة وأوقات بدء الدالة وانتهائها وبيان السجلّ الفعلي:

e8ba7d39c36df36c.png

يظهر في بيان التسجيل ما يلي: Processing file: pic-a-daily-architecture-events.png، ما يعني أنّه تم بالفعل تشغيل الحدث المرتبط بإنشاء هذه الصورة وتخزينها على النحو المتوقّع.

9. إعداد قاعدة البيانات

ستخزِّن معلومات حول الصورة التي توفّرها Vision API في قاعدة بيانات Cloud Firestore، وهي قاعدة بيانات مستنِدة إلى تنسيق NoSQL، وتتضمّن مستندات، وتتسم بالسرعة والإدارة الكاملة وبدون خادم، كما أنّها مستنِدة إلى السحابة الإلكترونية. جهِّز قاعدة البيانات من خلال الانتقال إلى القسم Firestore في Cloud Console:

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
  • اللون (سلسلة): رمز اللون السداسي العشري للون السائد (مثلاً #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 في عمود التنقّل على اليمين، ثم إنشاء فهرس مركّب كما هو موضّح أدناه:

ecb8b95e3c791272.png

انقر على Create. يمكن أن يستغرق إنشاء الفهرس بضع دقائق.

10. تعديل الدالة

ارجع إلى صفحة Functions لتعديل الدالة من أجل استدعاء Vision API لتحليل صورنا وتخزين البيانات الوصفية في Firestore.

من قائمة "الهامبرغر" (☰)، انتقِل إلى القسم Cloud Functions، وانقر على اسم الدالة، واختَر علامة التبويب Source، ثم انقر على الزر EDIT.

أولاً، عدِّل ملف package.json الذي يسرد التبعيات الخاصة بدالة Node.JS. عدِّل الرمز البرمجي لإضافة تبعية Cloud Vision API NPM:

{
  "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 وStorage و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();

الآن، ننتقل إلى بنية الدالة. نحوّلها إلى دالة غير متزامنة، لأنّنا نستخدم إمكانات async / await التي تم تقديمها في Node.js 8:

exports.vision_analysis = async (event, context) => {
    ...
    const filename = event.name;
    const filebucket = event.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:

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 هذه:

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، وانقر على الحزمة التي أنشأناها في بداية الدرس التطبيقي:

d44c1584122311c7.png

بعد الدخول إلى صفحة تفاصيل الحزمة، انقر على الزر Upload files لتحميل صورة.

26bb31d35fb6aa3d.png

من قائمة "الخطوط الثلاثة" (☰)، انتقِل إلى Logging > Logs Explorer.

في أداة الاختيار Log Fields، اختَر Cloud Function للاطّلاع على السجلات المخصّصة لوظائفك. انتقِل للأسفل خلال "حقول السجلّ"، ويمكنك حتى اختيار دالة معيّنة للحصول على عرض أكثر تفصيلاً لسجلات الدوال ذات الصلة. اختَر الدالة picture-uploaded.

b651dca7e25d5b11.png

في الواقع، يمكنني أن أرى في قائمة السجلّات أنّه تم استدعاء الدالة:

d22a7f24954e4f63.png

تشير السجلات إلى بداية تنفيذ الدالة ونهايته. وفي ما بينهما، يمكننا الاطّلاع على السجلات التي وضعناها في الدالة باستخدام عبارات console.log(). نلاحظ ما يلي:

  • تفاصيل الحدث الذي يؤدي إلى تشغيل الدالة
  • النتائج الأولية من طلب بيانات من واجهة برمجة التطبيقات Vision API
  • التصنيفات التي تم العثور عليها في الصورة التي حمّلناها
  • معلومات الألوان السائدة
  • تحدّد هذه السمة ما إذا كانت الصورة آمنة للعرض.
  • وفي النهاية، تم تخزين البيانات الوصفية الخاصة بالصورة في Firestore.

9ff7956a215c15da.png

انتقِل مرة أخرى من قائمة "الخطوط الثلاثة" (☰) إلى قسم Firestore. في القسم الفرعي Data (يظهر تلقائيًا)، من المفترض أن تظهر لك المجموعة pictures مع إضافة مستند جديد، بما يتوافق مع الصورة التي حمّلتها للتو:

a6137ab9687da370.png

14. التنظيف (اختياري)

إذا كنت لا تنوي مواصلة استخدام المختبرات الأخرى في السلسلة، يمكنك تنظيف الموارد لتوفير التكاليف ولتكون مواطنًا جيدًا للخدمات السحابية بشكل عام. يمكنك تنظيف الموارد بشكل فردي على النحو التالي.

حذف الحزمة:

gsutil rb gs://${BUCKET_PICTURES}

احذف الدالة:

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

احذف مجموعة Firestore من خلال اختيار "حذف المجموعة" (Delete collection) من المجموعة:

410b551c3264f70a.png

بدلاً من ذلك، يمكنك حذف المشروع بأكمله:

gcloud projects delete ${GOOGLE_CLOUD_PROJECT} 

15. تهانينا!

تهانينا! لقد نفّذت خدمة إدارة المفاتيح الأولى في المشروع بنجاح.

المواضيع التي تناولناها

  • Cloud Storage
  • وظائف السحابة الإلكترونية
  • Cloud Vision API
  • Cloud Firestore

الخطوات التالية