हर दिन फ़ोटो लेना: Lab 1—तस्वीरों को स्टोर करना और उनका विश्लेषण करना (Java)

1. खास जानकारी

पहले कोड लैब में, आपको बकेट में फ़ोटो अपलोड करनी होंगी. इससे फ़ाइल बनाने का एक इवेंट जनरेट होगा. इसे एक फ़ंक्शन हैंडल करेगा. यह फ़ंक्शन, इमेज का विश्लेषण करने के लिए Vision API को कॉल करेगा और नतीजों को डेटास्टोर में सेव करेगा.

d650ca5386ea71ad.png

आपको क्या सीखने को मिलेगा

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

2. सेटअप और ज़रूरी शर्तें

अपने हिसाब से एनवायरमेंट सेट अप करना

  1. Google Cloud Console में साइन इन करें और नया प्रोजेक्ट बनाएं या किसी मौजूदा प्रोजेक्ट का फिर से इस्तेमाल करें. अगर आपके पास पहले से कोई Gmail या Google Workspace खाता नहीं है, तो आपको एक खाता बनाना होगा.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • प्रोजेक्ट का नाम, इस प्रोजेक्ट में हिस्सा लेने वाले लोगों के लिए डिसप्ले नेम होता है. यह एक वर्ण स्ट्रिंग है, जिसका इस्तेमाल Google API नहीं करते. इसे कभी भी अपडेट किया जा सकता है.
  • प्रोजेक्ट आईडी, सभी 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 में चालू करना होगा.

Cloud Console में Vision API को चालू करने के लिए, खोज बार में Cloud Vision API खोजें:

cf48b1747ba6a6fb.png

आपको Cloud Vision API का पेज दिखेगा:

ba4af419e6086fbb.png

ENABLE बटन पर क्लिक करें.

इसके अलावा, gcloud कमांड लाइन टूल का इस्तेमाल करके, Cloud Shell में भी इसे चालू किया जा सकता है.

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 ( console.cloud.google.com) से किया जा सकता है. इसके अलावा, Cloud Shell या अपने लोकल डेवलपमेंट एनवायरमेंट से gsutil कमांड लाइन टूल का इस्तेमाल करके भी ऐसा किया जा सकता है.

"हैमबर्गर" (☰) मेन्यू में जाकर, 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)

बकेट बनाने के लिए, Cloud Shell में gsutil कमांड लाइन टूल का भी इस्तेमाल किया जा सकता है.

Cloud Shell में, बकेट के यूनीक नाम के लिए एक वैरिएबल सेट करें. Cloud Shell में, GOOGLE_CLOUD_PROJECT को पहले से ही आपके यूनीक प्रोजेक्ट आईडी पर सेट किया गया है. इसे बकेट के नाम में जोड़ा जा सकता है.

उदाहरण के लिए:

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 ऐक्सेस लेबल के साथ-साथ, आपको एक छोटा लिंक आइकॉन भी दिखेगा. इस पर क्लिक करने पर, आपका ब्राउज़र उस इमेज के सार्वजनिक यूआरएल पर नेविगेट करेगा. यह यूआरएल इस तरह का होगा:

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

इसमें BUCKET_NAME वह यूनीक नाम है जो आपने अपने बकेट के लिए चुना है. इसके बाद, आपकी फ़ोटो का फ़ाइल नाम है.

इमेज के नाम के बगल में मौजूद चेक बॉक्स पर क्लिक करने से, DELETE बटन चालू हो जाएगा. इसके बाद, पहली इमेज को मिटाया जा सकता है.

7. फ़ंक्शन बनाना

इस चरण में, आपको एक ऐसा फ़ंक्शन बनाना होता है जो फ़ोटो अपलोड करने से जुड़े इवेंट पर प्रतिक्रिया दे.

Google Cloud Console के Cloud Functions सेक्शन पर जाएं. इस पर जाने से, Cloud Functions सेवा अपने-आप चालू हो जाएगी.

9d29e8c026a7a53f.png

Create function पर क्लिक करें.

कोई नाम चुनें (जैसे, picture-uploaded) और क्षेत्र (ध्यान रखें कि बकेट के लिए, क्षेत्र का विकल्प एक जैसा हो):

4bb222633e6f278.png

फ़ंक्शन दो तरह के होते हैं:

  • एचटीटीपी फ़ंक्शन, जिन्हें यूआरएल (जैसे, वेब एपीआई) के ज़रिए चालू किया जा सकता है,
  • बैकग्राउंड फ़ंक्शन, जिन्हें किसी इवेंट से ट्रिगर किया जा सकता है.

आपको एक ऐसा बैकग्राउंड फ़ंक्शन बनाना है जो हमारे Cloud Storage बकेट में नई फ़ाइल अपलोड होने पर ट्रिगर हो:

d9a12fcf58f4813c.png

आपको Finalize/Create इवेंट टाइप में दिलचस्पी है. यह इवेंट तब ट्रिगर होता है, जब बकेट में कोई फ़ाइल बनाई जाती है या अपडेट की जाती है:

b30c8859b07dc4cb.png

पहले से बनाए गए बकेट को चुनें, ताकि Cloud Functions को सूचना मिल सके कि इस बकेट में कोई फ़ाइल कब बनाई गई / अपडेट की गई:

cb15a1f4c7a1ca5f.png

पहले बनाए गए बकेट को चुनने के लिए, Select पर क्लिक करें. इसके बाद, Save पर क्लिक करें

c1933777fac32c6a.png

अगला पर क्लिक करने से पहले, रनटाइम, बिल्ड, कनेक्शन, और सुरक्षा सेटिंग में जाकर, डिफ़ॉल्ट सेटिंग (256 एमबी मेमोरी) को बड़ा किया जा सकता है और उसमें बदलाव किया जा सकता है. इसके बाद, इसे 1 जीबी पर अपडेट किया जा सकता है.

83d757e6c38e10.png

Next पर क्लिक करने के बाद, रनटाइम, सोर्स कोड, और एंट्री पॉइंट को ट्यून किया जा सकता है.

इस फ़ंक्शन के लिए Inline editor को इस तरह रखें:

b6646ec646082b32.png

Java के किसी रनटाइम को चुनें. उदाहरण के लिए, Java 11:

f85b8a6f951f47a7.png

सोर्स कोड में एक Java फ़ाइल और एक pom.xml Maven फ़ाइल होती है. यह फ़ाइल, अलग-अलग मेटाडेटा और डिपेंडेंसी उपलब्ध कराती है.

कोड के डिफ़ॉल्ट स्निपेट को छोड़ दें: यह अपलोड की गई फ़ोटो के फ़ाइल नाम को लॉग करता है:

9b7b9801b42f6ca6.png

फ़िलहाल, टेस्टिंग के लिए फ़ंक्शन का नाम Example पर सेट करें.

फ़ंक्शन बनाने और उसे डिप्लॉय करने के लिए, Deploy पर क्लिक करें. डिप्लॉयमेंट पूरा होने के बाद, आपको फ़ंक्शन की सूची में हरे रंग के गोले में सही का निशान दिखेगा:

3732fdf409eefd1a.png

8. फ़ंक्शन की जांच करना

इस चरण में, यह टेस्ट करें कि फ़ंक्शन, स्टोरेज इवेंट का जवाब देता है या नहीं.

"हैमबर्गर" (☰) मेन्यू में जाकर, वापस Storage पेज पर जाएं.

इमेज बकेट पर क्लिक करें. इसके बाद, इमेज अपलोड करने के लिए Upload files पर क्लिक करें.

21767ec3cb8b18de.png

Logging > Logs Explorer पेज पर जाने के लिए, Cloud Console में फिर से नेविगेट करें.

Log Fields सिलेक्टर में, Cloud Function को चुनें. इससे आपको अपने फ़ंक्शन से जुड़े लॉग दिखेंगे. लॉग फ़ील्ड में नीचे की ओर स्क्रोल करें. साथ ही, फ़ंक्शन से जुड़े लॉग की ज़्यादा जानकारी देखने के लिए, कोई फ़ंक्शन भी चुना जा सकता है. picture-uploaded फ़ंक्शन चुनें.

आपको लॉग आइटम में ये चीज़ें दिखनी चाहिए: फ़ंक्शन बनाने की जानकारी, फ़ंक्शन शुरू और खत्म होने का समय, और हमारा असली लॉग स्टेटमेंट:

e8ba7d39c36df36c.png

हमारे लॉग स्टेटमेंट में यह लिखा है: Processing file: pic-a-daily-architecture-events.png. इसका मतलब है कि इस फ़ोटो को बनाने और सेव करने से जुड़ा इवेंट, उम्मीद के मुताबिक ट्रिगर हो गया है.

9. डेटाबेस तैयार करना

Vision API से मिली इमेज की जानकारी को Cloud Firestore डेटाबेस में सेव किया जाएगा. यह एक तेज़, पूरी तरह से मैनेज किया जाने वाला, बिना सर्वर वाला, क्लाउड-नेटिव NoSQL दस्तावेज़ डेटाबेस है. Cloud Console के Firestore सेक्शन में जाकर, अपना डेटाबेस तैयार करें:

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

हमारे कलेक्शन में प्रोग्राम के हिसाब से बनाए जाने वाले दस्तावेज़ों में चार फ़ील्ड होंगे:

  • name (string): अपलोड की गई फ़ोटो का फ़ाइल नाम. यह दस्तावेज़ की कुंजी भी है
  • labels (स्ट्रिंग की ऐरे): Vision API से पहचाने गए आइटम के लेबल
  • color (string): मुख्य रंग का हेक्साडेसिमल कलर कोड (जैसे, #ab12ef)
  • created (date): इस इमेज के मेटाडेटा को सेव किए जाने का टाइमस्टैंप
  • thumbnail (बूलियन): यह एक ज़रूरी नहीं है. अगर इस फ़ोटो के लिए थंबनेल इमेज जनरेट की गई है, तो यह फ़ील्ड मौजूद होगा और इसकी वैल्यू true होगी

हमें 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 बटन पर क्लिक करें.

सबसे पहले, pom.xml फ़ाइल में बदलाव करें. इस फ़ाइल में, हमारे Java फ़ंक्शन की डिपेंडेंसी की सूची दी गई है. 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;
    }
}

968749236c3f01da.png

11. फ़ंक्शन के बारे में जानें

आइए, अब वीडियो के कुछ दिलचस्प हिस्सों पर करीब से नज़र डालते हैं.

सबसे पहले, हम Maven pom.xml फ़ाइल में खास डिपेंडेंसी शामिल कर रहे हैं. Google Java Client Libraries, 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 फ़ंक्शन को ट्रिगर करने वाली फ़ाइल और बकेट का नाम कैसे वापस पाया जाता है.

रेफ़रंस के लिए, यहां इवेंट पेलोड का उदाहरण दिया गया है:

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

हम 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);
}

हम एक यूटिलिटी फ़ंक्शन का भी इस्तेमाल कर रहे हैं. इससे लाल / हरे / नीले रंग की वैल्यू को हेक्साडेसिमल कलर कोड में बदला जा सकता है. इस कोड का इस्तेमाल सीएसएस स्टाइलशीट में किया जा सकता है.

आइए, देखते हैं कि क्या इस फ़ोटो को दिखाया जा सकता है:

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. फ़ंक्शन डिप्लॉय करना

फ़ंक्शन को डिप्लॉय करने में लगने वाला समय.

604f47aa11fbf8e.png

DEPLOY बटन पर क्लिक करें. इसके बाद, नया वर्शन डिप्लॉय हो जाएगा. इसकी प्रोग्रेस यहां देखी जा सकती है:

13da63f23e4dbbdd.png

13. फ़ंक्शन को फिर से टेस्ट करें

फ़ंक्शन को डिप्लॉय करने के बाद, Cloud Storage में एक फ़ोटो पोस्ट करें. देखें कि हमारा फ़ंक्शन चालू हुआ है या नहीं, Vision API क्या दिखाता है, और Firestore में मेटाडेटा सेव किया गया है या नहीं.

Cloud Storage पर वापस जाएं और उस बकेट पर क्लिक करें जिसे हमने लैब की शुरुआत में बनाया था:

d44c1584122311c7.png

बकेट की जानकारी वाले पेज पर जाकर, फ़ोटो अपलोड करने के लिए Upload files बटन पर क्लिक करें.

26bb31d35fb6aa3d.png

"हैमबर्गर" (☰) मेन्यू में जाकर, Logging > Logs एक्सप्लोरर पर जाएं.

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 कलेक्शन मिटाएं:

410b551c3264f70a.png

इसके अलावा, पूरे प्रोजेक्ट को मिटाया जा सकता है:

gcloud projects delete ${GOOGLE_CLOUD_PROJECT} 

15. बधाई हो!

बधाई हो! आपने प्रोजेक्ट की पहली मुख्य सेवा को लागू कर दिया है!

हमने क्या-क्या बताया

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

अगले चरण