প্রতিদিনের ছবি: ল্যাব 1—ছবি সংরক্ষণ করুন এবং বিশ্লেষণ করুন (জাভা)

১. সংক্ষিপ্ত বিবরণ

প্রথম কোড ল্যাবে, আপনি একটি বাকেটে ছবি আপলোড করবেন। এর ফলে একটি ফাইল তৈরির ইভেন্ট তৈরি হবে, যা একটি ফাংশন দ্বারা পরিচালিত হবে। ফাংশনটি ইমেজ অ্যানালাইসিস করার জন্য ভিশন এপিআই-কে কল করবে এবং ফলাফল একটি ডেটাস্টোরে সংরক্ষণ করবে।

d650ca5386ea71ad.png

আপনি যা শিখবেন

  • ক্লাউড স্টোরেজ
  • ক্লাউড ফাংশন
  • ক্লাউড ভিশন এপিআই
  • ক্লাউড ফায়ারস্টোর

২. সেটআপ এবং প্রয়োজনীয়তা

স্ব-গতিতে পরিবেশ সেটআপ

  1. Google Cloud Console- এ সাইন-ইন করুন এবং একটি নতুন প্রজেক্ট তৈরি করুন অথবা বিদ্যমান কোনো প্রজেক্ট পুনরায় ব্যবহার করুন। যদি আপনার আগে থেকে Gmail বা Google Workspace অ্যাকাউন্ট না থাকে, তবে আপনাকে অবশ্যই একটি তৈরি করতে হবে।

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • প্রজেক্টের নামটি হলো এই প্রজেক্টের অংশগ্রহণকারীদের প্রদর্শিত নাম। এটি একটি ক্যারেক্টার স্ট্রিং যা গুগল এপিআই ব্যবহার করে না। আপনি যেকোনো সময় এটি আপডেট করতে পারেন।
  • সমস্ত গুগল ক্লাউড প্রজেক্ট জুড়ে প্রজেক্ট আইডি অবশ্যই অনন্য হতে হবে এবং এটি অপরিবর্তনীয় (একবার সেট করার পর পরিবর্তন করা যাবে না)। ক্লাউড কনসোল স্বয়ংক্রিয়ভাবে একটি অনন্য স্ট্রিং তৈরি করে; সাধারণত এটি কী তা নিয়ে আপনার মাথা ঘামানোর দরকার নেই। বেশিরভাগ কোডল্যাবে, আপনাকে প্রজেক্ট আইডি উল্লেখ করতে হবে (এটি সাধারণত PROJECT_ID হিসাবে চিহ্নিত করা হয়)। তৈরি করা আইডিটি আপনার পছন্দ না হলে, আপনি এলোমেলোভাবে আরেকটি তৈরি করতে পারেন। বিকল্পভাবে, আপনি নিজের আইডি দিয়ে চেষ্টা করে দেখতে পারেন যে সেটি উপলব্ধ আছে কিনা। এই ধাপের পরে এটি পরিবর্তন করা যাবে না এবং প্রজেক্টের পুরো সময়কাল জুড়ে এটি অপরিবর্তিত থাকবে।
  • আপনার অবগতির জন্য জানাচ্ছি যে, তৃতীয় একটি ভ্যালু রয়েছে, যা হলো প্রজেক্ট নাম্বার এবং কিছু এপিআই এটি ব্যবহার করে। ডকুমেন্টেশনে এই তিনটি ভ্যালু সম্পর্কে আরও বিস্তারিত জানুন।
  1. এরপর, ক্লাউড রিসোর্স/এপিআই ব্যবহার করার জন্য আপনাকে ক্লাউড কনসোলে বিলিং চালু করতে হবে। এই কোডল্যাবটি সম্পন্ন করতে খুব বেশি খরচ হওয়ার কথা নয়, এমনকি আদৌ কোনো খরচ নাও হতে পারে। এই টিউটোরিয়ালের পর যাতে কোনো বিলিং না হয়, সেজন্য রিসোর্সগুলো বন্ধ করতে আপনি আপনার তৈরি করা রিসোর্সগুলো অথবা পুরো প্রজেক্টটিই ডিলিট করে দিতে পারেন। গুগল ক্লাউডের নতুন ব্যবহারকারীরা ৩০০ মার্কিন ডলারের ফ্রি ট্রায়াল প্রোগ্রামের জন্য যোগ্য।

ক্লাউড শেল শুরু করুন

যদিও গুগল ক্লাউড আপনার ল্যাপটপ থেকে দূরবর্তীভাবে পরিচালনা করা যায়, এই কোডল্যাবে আপনি গুগল ক্লাউড শেল ব্যবহার করবেন, যা ক্লাউডে চালিত একটি কমান্ড লাইন পরিবেশ।

গুগল ক্লাউড কনসোল থেকে, উপরের ডানদিকের টুলবারে থাকা ক্লাউড শেল আইকনটিতে ক্লিক করুন:

55efc1aaa7a4d3ad.png

পরিবেশটি প্রস্তুত করতে এবং এর সাথে সংযোগ স্থাপন করতে মাত্র কয়েক মুহূর্ত সময় লাগবে। এটি শেষ হলে, আপনি এইরকম কিছু দেখতে পাবেন:

7ffe5cbb04455448.png

এই ভার্চুয়াল মেশিনটিতে আপনার প্রয়োজনীয় সমস্ত ডেভেলপমেন্ট টুলস লোড করা আছে। এটি একটি স্থায়ী ৫ জিবি হোম ডিরেক্টরি প্রদান করে এবং গুগল ক্লাউডে চলে, যা নেটওয়ার্ক পারফরম্যান্স ও অথেনটিকেশনকে ব্যাপকভাবে উন্নত করে। এই কোডল্যাবে আপনার সমস্ত কাজ একটি ব্রাউজারের মধ্যেই করা যাবে। আপনাকে কিছুই ইনস্টল করতে হবে না।

৩. এপিআই সক্রিয় করুন

এই ল্যাবের জন্য আপনারা ক্লাউড ফাংশন এবং ভিশন এপিআই ব্যবহার করবেন, কিন্তু তার আগে ক্লাউড কনসোল অথবা gcloud ব্যবহার করে এগুলোকে সক্রিয় করে নিতে হবে।

ক্লাউড কনসোলে ভিশন এপিআই সক্রিয় করতে, সার্চ বারে Cloud Vision API লিখে সার্চ করুন:

cf48b1747ba6a6fb.png

আপনি ক্লাউড ভিশন এপিআই পৃষ্ঠায় পৌঁছাবেন:

ba4af419e6086fbb.png

ENABLE বোতামটি ক্লিক করুন।

বিকল্পভাবে, আপনি gcloud কমান্ড লাইন টুল ব্যবহার করে ক্লাউড শেলেও এটি সক্রিয় করতে পারেন।

ক্লাউড শেলের ভিতরে, নিম্নলিখিত কমান্ডটি চালান:

gcloud services enable vision.googleapis.com

অপারেশনটি সফলভাবে সম্পন্ন হতে দেখা উচিত:

Operation "operations/acf.12dba18b-106f-4fd2-942d-fea80ecc5c1c" finished successfully.

ক্লাউড ফাংশনগুলোও সক্রিয় করুন:

gcloud services enable cloudfunctions.googleapis.com

৪. বাকেটটি তৈরি করুন (কনসোল)

ছবিগুলোর জন্য একটি স্টোরেজ বাকেট তৈরি করুন। আপনি এটি গুগল ক্লাউড প্ল্যাটফর্ম কনসোল ( console.cloud.google.com ) থেকে অথবা ক্লাউড শেল বা আপনার স্থানীয় ডেভেলপমেন্ট এনভায়রনমেন্ট থেকে 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 ক্লিক করুন।

সকল ব্যবহারকারীকে স্টোরেজ ভিউয়ার হিসেবে যুক্ত করুন

Permissions ট্যাবে যান:

d0ecfdcff730ea51.png

নিম্নোক্তভাবে বাকেটটিতে allUsers গ্রুপের একজন সদস্যকে Storage > Storage Object Viewer রোলে যুক্ত করুন:

e9f25ec1ea0b6cc6.png

SAVE ক্লিক করুন।

৫. বাকেটটি তৈরি করুন (gsutil)

এছাড়াও আপনি ক্লাউড শেলে gsutil কমান্ড লাইন টুল ব্যবহার করে বাকেট তৈরি করতে পারেন।

ক্লাউড শেলে, অনন্য বাকেট নামের জন্য একটি ভেরিয়েবল সেট করুন। ক্লাউড শেলে ইতিমধ্যেই 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

পূর্ববর্তী ধাপে ব্যাখ্যা অনুযায়ী, পরীক্ষা করে দেখুন যে আপনি বাকেটে ছবি আপলোড করতে পারছেন এবং আপলোড করা ছবিগুলো সর্বজনীনভাবে উপলব্ধ আছে।

৬. বাকেটটিতে সর্বসাধারণের প্রবেশাধিকার পরীক্ষা করুন।

স্টোরেজ ব্রাউজারে ফিরে গেলে, আপনি তালিকায় আপনার বাকেটটি দেখতে পাবেন, যেটির অ্যাক্সেস "পাবলিক" হিসেবে সেট করা থাকবে (সাথে একটি সতর্কীকরণ চিহ্নও থাকবে, যা আপনাকে মনে করিয়ে দেবে যে যে কেউ ওই বাকেটের কন্টেন্ট অ্যাক্সেস করতে পারবে)।

89e7a4d2c80a0319.png

আপনার বাকেটটি এখন ছবি গ্রহণের জন্য প্রস্তুত।

বাকেটের নামে ক্লিক করলে আপনি বাকেটের বিস্তারিত তথ্য দেখতে পাবেন।

131387f12d3eb2d3.png

সেখানে, আপনি বাকেটে একটি ছবি যোগ করতে পারেন কিনা তা পরীক্ষা করার জন্য Upload files বোতামটি ব্যবহার করে দেখতে পারেন। একটি ফাইল চুজারের পপআপ আপনাকে একটি ফাইল বেছে নিতে বলবে। একবার বেছে নেওয়া হলে, এটি আপনার বাকেটে আপলোড হয়ে যাবে এবং আপনি আবার সেই public অ্যাক্সেসটি দেখতে পাবেন যা এই নতুন ফাইলটিতে স্বয়ংক্রিয়ভাবে যুক্ত হয়ে গেছে।

e87584471a6e9c6d.png

Public অ্যাক্সেস’ লেবেলটির পাশে আপনি একটি ছোট লিঙ্ক আইকনও দেখতে পাবেন। সেটিতে ক্লিক করলে, আপনার ব্রাউজারটি সেই ছবিটির পাবলিক ইউআরএল-এ চলে যাবে, যা দেখতে এইরকম হবে:

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

এখানে BUCKET_NAME হলো আপনার বাকেটের জন্য বেছে নেওয়া বিশ্বব্যাপী অনন্য নাম, এবং তারপরে আপনার ছবির ফাইলের নাম।

ছবির নামের পাশে থাকা চেকবক্সে ক্লিক করলে DELETE বাটনটি সক্রিয় হবে এবং আপনি এই প্রথম ছবিটি মুছে ফেলতে পারবেন।

৭. ফাংশনটি তৈরি করুন।

এই ধাপে, আপনি এমন একটি ফাংশন তৈরি করবেন যা ছবি আপলোড ইভেন্টগুলোতে সাড়া দেবে।

গুগল ক্লাউড কনসোলের Cloud Functions বিভাগে যান। সেখানে গেলে ক্লাউড ফাংশনস পরিষেবাটি স্বয়ংক্রিয়ভাবে চালু হয়ে যাবে।

9d29e8c026a7a53f.png

Create function -এ ক্লিক করুন।

একটি নাম (যেমন, picture-uploaded ) এবং অঞ্চল নির্বাচন করুন (মনে রাখবেন, বাকেটের জন্য নির্বাচিত অঞ্চলের সাথে এটি যেন সামঞ্জস্যপূর্ণ থাকে):

4bb222633e6f278.png

দুই ধরনের ফাংশন আছে:

  • HTTP ফাংশন যা একটি URL (অর্থাৎ একটি ওয়েব API) এর মাধ্যমে আহ্বান করা যায়,
  • পটভূমি ফাংশন যা কোনো ঘটনার দ্বারা সক্রিয় হতে পারে।

আপনি এমন একটি ব্যাকগ্রাউন্ড ফাংশন তৈরি করতে চান যা আমাদের Cloud Storage বাকেটে একটি নতুন ফাইল আপলোড করা হলে চালু হবে:

d9a12fcf58f4813c.png

আপনি Finalize/Create ইভেন্ট টাইপটিতে আগ্রহী, যেটি বাকেটে কোনো ফাইল তৈরি বা আপডেট করা হলে ট্রিগার হয়:

b30c8859b07dc4cb.png

পূর্বে তৈরি করা বাকেটটি নির্বাচন করুন, যাতে এই নির্দিষ্ট বাকেটে কোনো ফাইল তৈরি বা আপডেট হলে ক্লাউড ফাংশনসকে জানানো যায়:

cb15a1f4c7a1ca5f.png

আপনার পূর্বে তৈরি করা বাকেটটি বেছে নিতে Select ক্লিক করুন এবং তারপর Save

c1933777fac32c6a.png

Next-এ ক্লিক করার আগে, আপনি Runtime, build, connections এবং security settings-এর অধীনে থাকা ডিফল্ট (২৫৬ এমবি মেমরি) এক্সপ্যান্ড ও মডিফাই করে সেটিকে ১ জিবি-তে আপডেট করতে পারেন।

83d757e6c38e10.png

Next ক্লিক করার পর আপনি রানটাইম , সোর্স কোড এবং এন্ট্রি পয়েন্ট পরিবর্তন করতে পারবেন।

এই ফাংশনের জন্য Inline editor রাখুন:

b6646ec646082b32.png

জাভা রানটাইমগুলোর মধ্যে একটি নির্বাচন করুন, যেমন জাভা ১১:

f85b8a6f951f47a7.png

সোর্স কোডটিতে একটি Java ফাইল এবং একটি pom.xml মেভেন ফাইল রয়েছে, যা বিভিন্ন মেটাডেটা ও ডিপেন্ডেন্সি সরবরাহ করে।

ডিফল্ট কোড অংশটি অপরিবর্তিত রাখুন: এটি আপলোড করা ছবির ফাইলের নাম লগ করে।

9b7b9801b42f6ca6.png

আপাতত, পরীক্ষার উদ্দেশ্যে, কার্যকর করার জন্য ফাংশনের নাম Example রাখুন।

ফাংশনটি তৈরি ও ডিপ্লয় করতে Deploy এ ক্লিক করুন। ডিপ্লয়মেন্ট সফল হলে, আপনি ফাংশনগুলোর তালিকায় একটি সবুজ বৃত্তের মধ্যে টিক চিহ্ন দেখতে পাবেন।

3732fdf409eefd1a.png

৮. ফাংশনটি পরীক্ষা করুন

এই ধাপে, ফাংশনটি স্টোরেজ ইভেন্টগুলিতে সাড়া দেয় কিনা তা পরীক্ষা করুন।

'হ্যামবার্গার' (☰) মেনু থেকে Storage পৃষ্ঠায় ফিরে যান।

ইমেজ বাকেটে ক্লিক করুন এবং তারপর একটি ছবি আপলোড করতে Upload files এ ক্লিক করুন।

21767ec3cb8b18de.png

ক্লাউড কনসোলের মধ্যে আবার Logging > Logs Explorer পৃষ্ঠায় যান।

Log Fields সিলেক্টরে, আপনার ফাংশনগুলোর জন্য নির্দিষ্ট লগগুলো দেখতে Cloud Function নির্বাচন করুন। লগ ফিল্ডস-এর মধ্যে স্ক্রল করে নিচে নামুন এবং আপনি একটি নির্দিষ্ট ফাংশন নির্বাচন করে সেই ফাংশন সম্পর্কিত লগগুলোর আরও বিস্তারিত দৃশ্য দেখতে পারেন। picture-uploaded ফাংশনটি নির্বাচন করুন।

আপনার লগ আইটেমগুলো দেখা উচিত যেখানে ফাংশনটি তৈরি করার কথা, ফাংশনটির শুরু ও শেষের সময় এবং আমাদের প্রকৃত লগ স্টেটমেন্টের উল্লেখ রয়েছে:

e8ba7d39c36df36c.png

আমাদের লগ স্টেটমেন্টে লেখা আছে: Processing file: pic-a-daily-architecture-events.png , যার অর্থ হলো, এই ছবিটি তৈরি এবং সংরক্ষণ সম্পর্কিত ইভেন্টটি প্রত্যাশা অনুযায়ীই ট্রিগার হয়েছে।

৯. ডাটাবেস প্রস্তুত করুন।

আপনি ভিশন এপিআই (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

আপনার কোনো ডকুমেন্ট তৈরি করার প্রয়োজন নেই। ক্লাউড স্টোরেজে নতুন ছবি জমা হওয়ার সাথে সাথে এবং ভিশন এপিআই (Vision API) দ্বারা সেগুলো বিশ্লেষণ হওয়ার পর, আপনি প্রোগ্রাম্যাটিকভাবেই সেগুলো যুক্ত করে নেবেন।

Save এ ক্লিক করুন।

ফায়ারস্টোর নতুন তৈরি করা কালেকশনে একটি প্রথম ডিফল্ট ডকুমেন্ট তৈরি করে, আপনি নিরাপদে সেই ডকুমেন্টটি মুছে ফেলতে পারেন কারণ এতে কোনো দরকারি তথ্য থাকে না:

5c2f1e17ea47f48f.png

আমাদের সংগ্রহে প্রোগ্রামগতভাবে যে নথিগুলো তৈরি করা হবে, সেগুলোতে ৪টি ফিল্ড থাকবে:

  • নাম (স্ট্রিং): আপলোড করা ছবির ফাইলের নাম, যা ডকুমেন্টটির কী-ও বটে।
  • লেবেল (স্ট্রিং-এর অ্যারে): ভিশন এপিআই দ্বারা শনাক্তকৃত আইটেমগুলোর লেবেল।
  • color (string): প্রধান রঙের হেক্সাডেসিমাল কোড (যেমন, #ab12ef)
  • তৈরি হওয়ার তারিখ: এই ছবিটির মেটাডেটা সংরক্ষণের সময়কার টাইমস্ট্যাম্প।
  • থাম্বনেইল (বুলিয়ান): একটি ঐচ্ছিক ক্ষেত্র যা তখনই উপস্থিত থাকবে এবং 'ট্রু' হবে, যদি এই ছবিটির জন্য একটি থাম্বনেইল ছবি তৈরি করা হয়ে থাকে।

যেহেতু আমরা ফায়ারস্টোরে থাম্বনেইলসহ ছবি খুঁজব এবং তৈরির তারিখ অনুসারে সাজাব, তাই আমাদের একটি সার্চ ইনডেক্স তৈরি করতে হবে।

আপনি ক্লাউড শেলে নিম্নলিখিত কমান্ডের মাধ্যমে ইনডেক্সটি তৈরি করতে পারেন:

gcloud firestore indexes composite create \
  --collection-group=pictures \
  --field-config field-path=thumbnail,order=descending \
  --field-config field-path=created,order=descending

অথবা আপনি ক্লাউড কনসোল থেকেও এটি করতে পারেন। এর জন্য, বাম দিকের নেভিগেশন কলামে থাকা Indexes এ ক্লিক করে, নিচে দেখানো পদ্ধতি অনুযায়ী একটি কম্পোজিট ইনডেক্স তৈরি করুন:

ecb8b95e3c791272.png

Create এ ক্লিক করুন। ইনডেক্স তৈরি হতে কয়েক মিনিট সময় লাগতে পারে।

১০. ফাংশনটি আপডেট করুন।

আমাদের ছবিগুলো বিশ্লেষণ করার জন্য ভিশন এপিআই (Vision API) কল করতে এবং ফায়ারস্টোরে (Firestore) মেটাডেটা সংরক্ষণ করতে ফাংশনটি আপডেট করার জন্য Functions পৃষ্ঠায় ফিরে যান।

'হ্যামবার্গার' (☰) মেনু থেকে Cloud Functions বিভাগে যান, ফাংশনের নামে ক্লিক করুন, Source ট্যাবটি নির্বাচন করুন এবং তারপরে EDIT বোতামে ক্লিক করুন।

প্রথমে, pom.xml ফাইলটি সম্পাদনা করুন, যেখানে আমাদের জাভা ফাংশনের নির্ভরতাগুলির তালিকা রয়েছে। 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

১১. কার্যকারিতাটি অন্বেষণ করুন।

চলুন বিভিন্ন আকর্ষণীয় অংশগুলো আরও ভালোভাবে দেখে নেওয়া যাক।

প্রথমে, আমরা Maven pom.xml ফাইলে নির্দিষ্ট ডিপেন্ডেন্সিগুলো অন্তর্ভুক্ত করছি । যেকোনো ডিপেন্ডেন্সি দ্বন্দ্ব দূর করার জন্য, গুগল জাভা ক্লায়েন্ট লাইব্রেরি একটি Bill-of-Materials(BOM) প্রকাশ করে। এটি ব্যবহার করলে, আপনাকে স্বতন্ত্র গুগল ক্লায়েন্ট লাইব্রেরিগুলোর জন্য কোনো সংস্করণ নির্দিষ্ট করতে হবে না।

  <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>

তারপর, আমরা ভিশন এপিআই-এর জন্য একজন ক্লায়েন্টকে প্রস্তুত করি:

...
try (ImageAnnotatorClient vision = ImageAnnotatorClient.create()) {
...

এবার আসে আমাদের ফাংশনের কাঠামো। আমরা আগত ইভেন্ট থেকে আমাদের প্রয়োজনীয় ফিল্ডগুলো গ্রহণ করি এবং সেগুলোকে আমাদের সংজ্ঞায়িত GCSEvent কাঠামোতে ম্যাপ করি:

...
public class ImageAnalysis implements BackgroundFunction<GCSEvent> {
    @Override
    public void accept(GCSEvent event, Context context) 
            throws IOException, InterruptedException,     
    ExecutionException {
...

    public static class GCSEvent {
        String bucket;
        String name;
    }

সিগনেচারটি লক্ষ্য করুন, তবে তার সাথে এটাও লক্ষ্য করুন যে আমরা কীভাবে সেই ফাইল এবং বাকেটের নাম পুনরুদ্ধার করি যা ক্লাউড ফাংশনটিকে ট্রিগার করেছে।

আপনার সুবিধার জন্য, ইভেন্ট পেলোডটি দেখতে এইরকম:

{
  "bucket":"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"
}

আমরা ভিশন ক্লায়েন্টের মাধ্যমে পাঠানোর জন্য একটি অনুরোধ প্রস্তুত করি:

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

আমরা ভিশন এপিআই-এর তিনটি প্রধান সক্ষমতা জানতে চাইছি:

  • লেবেল শনাক্তকরণ : ঐ ছবিগুলোতে কী আছে তা বোঝার জন্য
  • ছবির বৈশিষ্ট্য : ছবির আকর্ষণীয় গুণাবলী তুলে ধরতে (আমরা ছবির প্রধান রঙটি জানতে আগ্রহী)
  • নিরাপদ অনুসন্ধান : ছবিটি দেখানোর জন্য নিরাপদ কিনা তা জানতে (এতে প্রাপ্তবয়স্ক / চিকিৎসা সংক্রান্ত / উত্তেজক / হিংসাত্মক বিষয়বস্তু থাকা উচিত নয়)

এই পর্যায়ে, আমরা ভিশন এপিআই-কে কল করতে পারি:

...
logger.info("Calling the Vision API...");
BatchAnnotateImagesResponse result = 
                            vision.batchAnnotateImages(requests);
List<AnnotateImageResponse> responses = result.getResponsesList();
...

তথ্যসূত্র হিসেবে, ভিশন এপিআই থেকে প্রাপ্ত প্রতিক্রিয়াটি দেখতে এইরকম:

{
  "faceAnnotations": [],
  "landmarkAnnotations": [],
  "logoAnnotations": [],
  "labelAnnotations": [
    {
      "locations": [],
      "properties": [],
      "mid": "/m/01yrx",
      "locale": "",
      "description": "Cat",
      "score": 0.9959855675697327,
      "confidence": 0,
      "topicality": 0.9959855675697327,
      "boundingPoly": null
    },
     - - - 
  ],
  "textAnnotations": [],
  "localizedObjectAnnotations": [],
  "safeSearchAnnotation": {
    "adult": "VERY_UNLIKELY",
    "spoof": "UNLIKELY",
    "medical": "VERY_UNLIKELY",
    "violence": "VERY_UNLIKELY",
    "racy": "VERY_UNLIKELY",
    "adultConfidence": 0,
    "spoofConfidence": 0,
    "medicalConfidence": 0,
    "violenceConfidence": 0,
    "racyConfidence": 0,
    "nsfwConfidence": 0
  },
  "imagePropertiesAnnotation": {
    "dominantColors": {
      "colors": [
        {
          "color": {
            "red": 203,
            "green": 201,
            "blue": 201,
            "alpha": null
          },
          "score": 0.4175916016101837,
          "pixelFraction": 0.44456374645233154
        },
         - - - 
      ]
    }
  },
  "error": null,
  "cropHintsAnnotation": {
    "cropHints": [
      {
        "boundingPoly": {
          "vertices": [
            { "x": 0, "y": 118 },
            { "x": 1177, "y": 118 },
            { "x": 1177, "y": 783 },
            { "x": 0, "y": 783 }
          ],
          "normalizedVertices": []
        },
        "confidence": 0.41695669293403625,
        "importanceFraction": 1
      }
    ]
  },
  "fullTextAnnotation": null,
  "webDetection": null,
  "productSearchResults": null,
  "context": null
}

যদি কোনো ত্রুটি ফেরত না আসে, তাহলে আমরা এগিয়ে যেতে পারি, আর এই কারণেই আমাদের এই if ব্লকটি রয়েছে:

AnnotateImageResponse response = responses.get(0);
if (response.hasError()) {
     logger.info("Error: " + response.getError().getMessage());
     return;
}

আমরা ছবিতে শনাক্ত করা জিনিস, বিভাগ বা বিষয়বস্তুর নামগুলো জেনে নেব:

List<String> labels = response.getLabelAnnotationsList().stream()
    .map(annotation -> annotation.getDescription())
    .collect(Collectors.toList());

logger.info("Annotations found:");
for (String label: labels) {
    logger.info("- " + label);
}

আমরা ছবিটির প্রধান রঙটি জানতে আগ্রহী:

String mainColor = "#FFFFFF";
ImageProperties imgProps = response.getImagePropertiesAnnotation();
if (imgProps.hasDominantColors()) {
    DominantColorsAnnotation colorsAnn = 
                               imgProps.getDominantColors();
    ColorInfo colorInfo = colorsAnn.getColors(0);

    mainColor = rgbHex(
        colorInfo.getColor().getRed(), 
        colorInfo.getColor().getGreen(), 
        colorInfo.getColor().getBlue());

    logger.info("Color: " + mainColor);
}

এছাড়াও আমরা একটি ইউটিলিটি ফাংশন ব্যবহার করছি, যা লাল / সবুজ / নীল মানগুলোকে একটি হেক্সাডেসিমাল কালার কোডে রূপান্তর করে, যা আমরা CSS স্টাইলশীটে ব্যবহার করতে পারি।

চলুন দেখে নেওয়া যাক ছবিটি দেখানো নিরাপদ কিনা:

boolean isSafe = false;
if (response.hasSafeSearchAnnotation()) {
    SafeSearchAnnotation safeSearch = 
                      response.getSafeSearchAnnotation();

    isSafe = Stream.of(
        safeSearch.getAdult(), safeSearch.getMedical(), safeSearch.getRacy(),
        safeSearch.getSpoof(), safeSearch.getViolence())
    .allMatch( likelihood -> 
        likelihood != Likelihood.LIKELY && likelihood != Likelihood.VERY_LIKELY
    );

    logger.info("Safe? " + isSafe);
}

আমরা প্রাপ্তবয়স্ক / ব্যঙ্গাত্মক / চিকিৎসা সংক্রান্ত / সহিংস / উত্তেজক বৈশিষ্ট্যগুলো যাচাই করে দেখছি যে সেগুলোর সম্ভাবনা কম নাকি খুব বেশি

নিরাপদ অনুসন্ধানের ফলাফল ঠিক থাকলে, আমরা ফায়ারস্টোরে মেটাডেটা সংরক্ষণ করতে পারি:

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());
}

১২. ফাংশনটি স্থাপন করুন

ফাংশনটি স্থাপন করার সময় হয়েছে।

604f47aa11fbf8e.png

DEPLOY বোতামে চাপ দিন এবং নতুন সংস্করণটি ডেপ্লয় হয়ে যাবে, আপনি এর অগ্রগতি দেখতে পারবেন:

13da63f23e4dbbdd.png

১৩. ফাংশনটি পুনরায় পরীক্ষা করুন।

ফাংশনটি সফলভাবে ডেপ্লয় হয়ে গেলে, আপনি ক্লাউড স্টোরেজে একটি ছবি পোস্ট করবেন, দেখবেন আমাদের ফাংশনটি কল করা হয়েছে কিনা, ভিশন এপিআই কী রিটার্ন করছে, এবং মেটাডেটা ফায়ারস্টোরে সংরক্ষিত হয়েছে কিনা।

Cloud Storage ফিরে যান এবং ল্যাবের শুরুতে আমরা যে বাকেটটি তৈরি করেছিলাম সেটিতে ক্লিক করুন:

d44c1584122311c7.png

বাকেট ডিটেইলস পেজে প্রবেশ করার পর, একটি ছবি আপলোড করার জন্য Upload files বাটনে ক্লিক করুন।

26bb31d35fb6aa3d.png

'হ্যামবার্গার' (☰) মেনু থেকে, Logging > Logs এক্সপ্লোরার-এ যান।

Log Fields সিলেক্টরে, আপনার ফাংশনগুলোর জন্য নির্দিষ্ট লগগুলো দেখতে Cloud Function নির্বাচন করুন। লগ ফিল্ডস-এর মধ্যে স্ক্রল করে নিচে নামুন এবং আপনি একটি নির্দিষ্ট ফাংশন নির্বাচন করে সেই ফাংশন সম্পর্কিত লগগুলোর আরও বিস্তারিত দৃশ্য দেখতে পারেন। picture-uploaded ফাংশনটি নির্বাচন করুন।

b651dca7e25d5b11.png

এবং প্রকৃতপক্ষে, লগগুলির তালিকায় আমি দেখতে পাচ্ছি যে আমাদের ফাংশনটি কল করা হয়েছিল:

d22a7f24954e4f63.png

লগগুলো ফাংশন নির্বাহের শুরু এবং শেষ নির্দেশ করে। এবং এর মাঝে, আমরা আমাদের ফাংশনে console.log() স্টেটমেন্ট দিয়ে রাখা লগগুলো দেখতে পারি। আমরা দেখতে পাই:

  • যে ঘটনাটি আমাদের ফাংশনটিকে সক্রিয় করে তার বিবরণ,
  • ভিশন এপিআই কল থেকে প্রাপ্ত কাঁচা ফলাফল,
  • আমরা যে ছবিটি আপলোড করেছিলাম, তাতে যে লেবেলগুলো পাওয়া গিয়েছিল,
  • প্রভাবশালী রঙের তথ্য,
  • ছবিটি দেখানো নিরাপদ কিনা,
  • এবং অবশেষে ছবিটির মেটাডেটা ফায়ারস্টোরে সংরক্ষণ করা হয়েছে।

9ff7956a215c15da.png

আবার 'হ্যামবার্গার' (☰) মেনু থেকে, Firestore বিভাগে যান। Data উপবিভাগে (যা ডিফল্টরূপে দেখানো হয়), আপনি pictures কালেকশনটি দেখতে পাবেন, যেখানে আপনার এইমাত্র আপলোড করা ছবিটির সাথে সম্পর্কিত একটি নতুন ডকুমেন্ট যুক্ত থাকবে:

a6137ab9687da370.png

১৪. পরিষ্কার করা (ঐচ্ছিক)

আপনি যদি এই সিরিজের অন্য ল্যাবগুলো চালিয়ে যেতে না চান, তাহলে খরচ বাঁচাতে এবং সার্বিকভাবে একজন ভালো ক্লাউড ব্যবহারকারী হতে রিসোর্সগুলো পরিষ্কার করতে পারেন। আপনি নিম্নলিখিত উপায়ে একে একে রিসোর্সগুলো পরিষ্কার করতে পারেন।

বাকেটটি মুছে ফেলুন:

gsutil rb gs://${BUCKET_PICTURES}

ফাংশনটি মুছে ফেলুন:

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

কালেকশন থেকে 'ডিলিট কালেকশন' নির্বাচন করে ফায়ারস্টোর কালেকশনটি মুছে ফেলুন:

410b551c3264f70a.png

বিকল্পভাবে, আপনি পুরো প্রজেক্টটি মুছে ফেলতে পারেন:

gcloud projects delete ${GOOGLE_CLOUD_PROJECT} 

১৫. অভিনন্দন!

অভিনন্দন! আপনি প্রকল্পের প্রথম মূল পরিষেবাটি সফলভাবে বাস্তবায়ন করেছেন!

আমরা যা আলোচনা করেছি

  • ক্লাউড স্টোরেজ
  • ক্লাউড ফাংশন
  • ক্লাউড ভিশন এপিআই
  • ক্লাউড ফায়ারস্টোর

পরবর্তী পদক্ষেপ