LiteRT এর সাথে Android-এ রিয়েল-টাইম অ্যাক্সিলারেটেড ইমেজ সেগমেন্টেশন

১. শুরু করার আগে

কোড টাইপ করা মাসল মেমোরি তৈরি করার এবং বিষয়বস্তু সম্পর্কে আপনার বোঝাপড়া গভীর করার একটি চমৎকার উপায়। যদিও কপি-পেস্ট করা সময় বাঁচাতে পারে, দীর্ঘমেয়াদে এই অভ্যাসে বিনিয়োগ করলে তা আরও বেশি কর্মদক্ষতা এবং শক্তিশালী কোডিং দক্ষতার দিকে নিয়ে যেতে পারে।

এই কোডল্যাবে, আপনি শিখবেন কীভাবে গুগলের টেনসরফ্লো লাইট (TensorFlow Lite)-এর নতুন রানটাইম, LiteRT ব্যবহার করে একটি লাইভ ক্যামেরা ফিডের উপর রিয়েল-টাইম ইমেজ সেগমেন্টেশন সম্পাদনকারী একটি অ্যান্ড্রয়েড অ্যাপ্লিকেশন তৈরি করতে হয়। আপনি একটি স্টার্টার অ্যান্ড্রয়েড অ্যাপ্লিকেশন নেবেন এবং এতে ইমেজ সেগমেন্টেশন ক্ষমতা যুক্ত করবেন। আমরা প্রিপ্রসেসিং, ইনফারেন্স এবং পোস্টপ্রসেসিং ধাপগুলোও দেখব। আপনি যা যা করবেন:

  • এমন একটি অ্যান্ড্রয়েড অ্যাপ তৈরি করুন যা রিয়েল-টাইমে ছবিকে বিভিন্ন অংশে ভাগ করতে পারে।
  • একটি পূর্ব-প্রশিক্ষিত LiteRT চিত্র বিভাজন মডেল সংহত করুন।
  • মডেলের জন্য ইনপুট ছবিটি প্রাক-প্রক্রিয়াজাত করুন।
  • সিপিইউ এবং জিপিইউ অ্যাক্সিলারেশনের জন্য LiteRT রানটাইম ব্যবহার করুন।
  • সেগমেন্টেশন মাস্ক প্রদর্শন করার জন্য মডেলের আউটপুট কীভাবে প্রসেস করতে হয় তা বুঝুন।
  • ফ্রন্ট-ফেসিং ক্যামেরাটি কীভাবে অ্যাডজাস্ট করতে হয় তা বুঝুন।

সবশেষে, আপনি নিচের ছবির মতো কিছু একটা তৈরি করবেন:

অ্যাপটি শেষ হয়েছে

পূর্বশর্ত

এই কোডল্যাবটি অভিজ্ঞ মোবাইল ডেভেলপারদের জন্য ডিজাইন করা হয়েছে, যারা মেশিন লার্নিং-এ অভিজ্ঞতা অর্জন করতে চান। আপনার নিম্নলিখিত বিষয়গুলিতে পরিচিতি থাকা উচিত:

  • কোটলিন এবং অ্যান্ড্রয়েড স্টুডিও ব্যবহার করে অ্যান্ড্রয়েড ডেভেলপমেন্ট
  • ইমেজ প্রসেসিংয়ের মৌলিক ধারণা

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

  • একটি অ্যান্ড্রয়েড অ্যাপ্লিকেশনে LiteRT রানটাইম কীভাবে সংহত ও ব্যবহার করবেন।
  • প্রি-ট্রেইনড LiteRT মডেল ব্যবহার করে কীভাবে ইমেজ সেগমেন্টেশন করা যায়।
  • মডেলের জন্য ইনপুট ইমেজকে কীভাবে প্রিপ্রসেস করতে হয়।
  • মডেলের জন্য ইনফারেন্স কীভাবে চালাতে হয়।
  • ফলাফলগুলো দৃশ্যমান করার জন্য একটি সেগমেন্টেশন মডেলের আউটপুট কীভাবে প্রসেস করতে হয়।
  • রিয়েল-টাইম ক্যামেরা ফিড প্রসেসিংয়ের জন্য CameraX কীভাবে ব্যবহার করবেন।

আপনার যা যা লাগবে

  • অ্যান্ড্রয়েড স্টুডিও-র একটি সাম্প্রতিক সংস্করণ (v2025.1.1-এ পরীক্ষিত)।
  • একটি বাস্তব অ্যান্ড্রয়েড ডিভাইস। এটি গ্যালাক্সি এবং পিক্সেল ডিভাইসে সবচেয়ে ভালোভাবে পরীক্ষা করা যায়।
  • নমুনা কোডটি (গিটহাব থেকে নেওয়া)।
  • কোটলিনে অ্যান্ড্রয়েড ডেভেলপমেন্টের প্রাথমিক জ্ঞান।

২. চিত্র বিভাজন

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

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

বিভাজন উদাহরণ

LiteRT: অন-ডিভাইস এমএল-এর সীমানা প্রসারিত করা

মোবাইল ডিভাইসে রিয়েল-টাইম ও উচ্চ-মানের সেগমেন্টেশন সক্ষমকারী একটি মূল প্রযুক্তি হলো LiteRT । TensorFlow Lite-এর জন্য গুগলের পরবর্তী প্রজন্মের, উচ্চ-ক্ষমতাসম্পন্ন রানটাইম হিসেবে LiteRT-কে অন্তর্নিহিত হার্ডওয়্যার থেকে সর্বোত্তম পারফরম্যান্স পাওয়ার জন্য ডিজাইন করা হয়েছে।

এটি GPU (গ্রাফিক্স প্রসেসিং ইউনিট) এবং NPU (নিউরাল প্রসেসিং ইউনিট)-এর মতো হার্ডওয়্যার অ্যাক্সিলারেটরগুলির বুদ্ধিদীপ্ত ও অপ্টিমাইজড ব্যবহারের মাধ্যমে এই কাজটি সম্পন্ন করে। সেগমেন্টেশন মডেলের তীব্র গণনার কাজের চাপ সাধারণ সিপিইউ থেকে এই বিশেষায়িত প্রসেসরগুলিতে সরিয়ে নেওয়ার মাধ্যমে LiteRT ইনফারেন্স টাইম নাটকীয়ভাবে কমিয়ে দেয়। এই অ্যাক্সিলারেশনের কারণেই লাইভ ক্যামেরা ফিডে জটিল মডেলগুলি মসৃণভাবে চালানো সম্ভব হয়, যা সরাসরি আপনার ফোনে মেশিন লার্নিংয়ের মাধ্যমে আমাদের অর্জনের পরিধিকে প্রসারিত করে। এই স্তরের পারফরম্যান্স ছাড়া, রিয়েল-টাইম সেগমেন্টেশন একটি ভালো ইউজার এক্সপেরিয়েন্সের জন্য খুবই ধীর এবং খাপছাড়া হতো।

৩. প্রস্তুত হন

রিপোজিটরি ক্লোন করুন

প্রথমে, LiteRT-এর রিপোজিটরি ক্লোন করুন:

git clone https://github.com/google-ai-edge/litert-samples.git

litert-samples/compiled_model_api/image_segmentation হলো সেই ডিরেক্টরি যেখানে আপনার প্রয়োজনীয় সমস্ত রিসোর্স রয়েছে। এই কোডল্যাবের জন্য, আপনার শুধুমাত্র kotlin_cpu_gpu/android_starter প্রজেক্টটি প্রয়োজন হবে। যদি আপনি কোথাও আটকে যান, তবে সম্পূর্ণ প্রজেক্টটি দেখে নিতে পারেন: kotlin_cpu_gpu/android

ফাইল পাথ সম্পর্কে একটি নোট

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

অ্যান্ড্রয়েড স্টুডিও প্রজেক্ট ভিউ এবং একটি সাধারণ ফাইল সিস্টেম ভিউ-এর মধ্যে পার্থক্যটিও লক্ষ্য করা গুরুত্বপূর্ণ। অ্যান্ড্রয়েড স্টুডিও প্রজেক্ট ভিউ হলো আপনার প্রজেক্টের ফাইলগুলোর একটি কাঠামোগত উপস্থাপনা, যা অ্যান্ড্রয়েড ডেভেলপমেন্টের জন্য সাজানো থাকে। এই টিউটোরিয়ালে উল্লেখিত ফাইল পাথগুলো ফাইল সিস্টেমের পাথকে বোঝায়, অ্যান্ড্রয়েড স্টুডিও প্রজেক্ট ভিউ-এর পাথকে নয়।

স্টার্টার অ্যাপটি ইম্পোর্ট করুন

প্রথমে অ্যান্ড্রয়েড স্টুডিওতে স্টার্টার অ্যাপটি ইম্পোর্ট করে নেওয়া যাক।

  1. অ্যান্ড্রয়েড স্টুডিও খুলুন এবং ওপেন নির্বাচন করুন।

অ্যান্ড্রয়েড স্টুডিও ওপেন

  1. kotlin_cpu_gpu/android_starter ডিরেক্টরিতে যান এবং এটি খুলুন।

অ্যান্ড্রয়েড স্টার্টার

আপনার অ্যাপে সমস্ত ডিপেন্ডেন্সি উপলব্ধ আছে কিনা তা নিশ্চিত করতে, ইম্পোর্ট প্রক্রিয়া শেষ হলে আপনার প্রজেক্টটি গ্রেডল ফাইলের সাথে সিঙ্ক করা উচিত।

  1. অ্যান্ড্রয়েড স্টুডিও টুলবার থেকে ‘Sync Project with Gradle Files’ নির্বাচন করুন।

মেনু সিঙ্ক

  1. অনুগ্রহ করে এই ধাপটি এড়িয়ে যাবেন না – এটি কাজ না করলে টিউটোরিয়ালের বাকি অংশ বোঝা যাবে না।

স্টার্টার অ্যাপটি চালান

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

আপনার অ্যান্ড্রয়েড ডিভাইসটি ইউএসবি-র মাধ্যমে কম্পিউটারের সাথে সংযুক্ত করুন এবং অ্যান্ড্রয়েড স্টুডিও টুলবারে থাকা 'রান' (Run) বোতামে ক্লিক করুন।

রান বাটন

অ্যাপটি আপনার ডিভাইসে চালু হবে। আপনি একটি লাইভ ক্যামেরা ফিড দেখতে পাবেন, কিন্তু তখনও কোনো সেগমেন্টেশন হবে না। এই টিউটোরিয়ালে আপনি যে সমস্ত ফাইল এডিট করবেন, সেগুলি litert-samples/compiled_model_api/image_segmentation/kotlin_cpu_gpu/android_starter/app/src/main/java/com/google/ai/edge/examples/image_segmentation ডিরেক্টরির অধীনে থাকবে (এখন আপনি জানেন কেন অ্যান্ড্রয়েড স্টুডিও এটির পুনর্গঠন করে 😃)।

প্রকল্প পরিচালক

আপনি ImageSegmentationHelper.kt , MainViewModel.kt , এবং view/SegmentationOverlay.kt ফাইলগুলোতেও TODO কমেন্ট দেখতে পাবেন। পরবর্তী ধাপগুলোতে, আপনি এই TODO গুলো পূরণ করে ইমেজ সেগমেন্টেশন কার্যকারিতাটি বাস্তবায়ন করবেন।

৪. স্টার্টার অ্যাপটি বুঝুন

স্টার্টার অ্যাপটিতে আগে থেকেই একটি বেসিক ইউআই এবং ক্যামেরা পরিচালনার লজিক রয়েছে। এখানে মূল ফাইলগুলোর একটি সংক্ষিপ্ত বিবরণ দেওয়া হলো:

  • app/src/main/java/com/google/ai/edge/examples/image_segmentation/MainActivity.kt : এটি অ্যাপ্লিকেশনটির প্রধান প্রবেশপথ। এটি Jetpack Compose ব্যবহার করে UI সেট আপ করে এবং ক্যামেরার অনুমতিগুলো পরিচালনা করে।
  • app/src/main/java/com/google/ai/edge/examples/image_segmentation/MainViewModel.kt : এই ViewModel-টি UI স্টেট পরিচালনা করে এবং ইমেজ সেগমেন্টেশন প্রক্রিয়াটি সমন্বয় করে।
  • app/src/main/java/com/google/ai/edge/examples/image_segmentation/ImageSegmentationHelper.kt : এখানেই আমরা ইমেজ সেগমেন্টেশনের মূল লজিক যোগ করব। এটি মডেল লোড করা, ক্যামেরা ফ্রেম প্রসেস করা এবং ইনফারেন্স চালানোর কাজ করবে।
  • app/src/main/java/com/google/ai/edge/examples/image_segmentation/view/CameraScreen.kt : এই Composable ফাংশনটি ক্যামেরা প্রিভিউ এবং সেগমেন্টেশন ওভারলে প্রদর্শন করে।
  • app/download_model.gradle : এই স্ক্রিপ্টটি selfie_multiclass.tflite ফাইলটি ডাউনলোড করে। এটি হলো প্রি-ট্রেইনড TensorFlow Lite ইমেজ সেগমেন্টেশন মডেল যা আমরা ব্যবহার করব।

৫. LiteRT বোঝা এবং ডিপেন্ডেন্সি যোগ করা

এখন, স্টার্টার অ্যাপটিতে ইমেজ সেগমেন্টেশন কার্যকারিতাটি যোগ করা যাক।

১. LiteRT নির্ভরতা যোগ করুন

প্রথমত, আপনাকে আপনার প্রজেক্টে LiteRT লাইব্রেরিটি যুক্ত করতে হবে। গুগলের অপ্টিমাইজ করা রানটাইম ব্যবহার করে ডিভাইসে মেশিন লার্নিং চালু করার জন্য এটিই সবচেয়ে গুরুত্বপূর্ণ প্রথম ধাপ।

app/build.gradle.kts ফাইলটি খুলুন এবং dependencies ব্লকে নিম্নলিখিত লাইনটি যোগ করুন:

// LiteRT for on-device ML
implementation(libs.litert)

ডিপেন্ডেন্সি যোগ করার পর, অ্যান্ড্রয়েড স্টুডিওর উপরের ডান কোণায় প্রদর্শিত 'সিঙ্ক নাউ' বোতামে ক্লিক করে আপনার প্রজেক্টকে গ্রেডল ফাইলের সাথে সিঙ্ক করুন।

এখনই সিঙ্ক করুন

২. মূল LiteRT API-গুলো বুঝুন

ImageSegmentationHelper.kt খুলুন

ইমপ্লিমেন্টেশন কোড লেখার আগে, আপনি যে LiteRT API ব্যবহার করবেন তার মূল উপাদানগুলো বোঝা জরুরি। নিশ্চিত করুন যে আপনি com.google.ai.edge.litert প্যাকেজ থেকে ইম্পোর্ট করছেন, এবং ImageSegmentationHelper.kt ফাইলের শুরুতে নিম্নলিখিত ইম্পোর্টগুলো যোগ করুন:

import com.google.ai.edge.litert.Accelerator
import com.google.ai.edge.litert.CompiledModel
  • CompiledModel : এটি আপনার TFLite মডেলের সাথে যোগাযোগের জন্য কেন্দ্রীয় ক্লাস। এটি এমন একটি মডেলকে উপস্থাপন করে যা একটি নির্দিষ্ট হার্ডওয়্যার অ্যাক্সেলারেটরের (যেমন CPU বা GPU) জন্য আগে থেকেই কম্পাইল এবং অপ্টিমাইজ করা হয়েছে। এই প্রি-কম্পাইলেশন LiteRT-এর একটি প্রধান বৈশিষ্ট্য যা দ্রুততর এবং আরও কার্যকর ইনফারেন্সে সহায়তা করে।
  • CompiledModel.Options : আপনি CompiledModel কনফিগার করার জন্য এই বিল্ডার ক্লাসটি ব্যবহার করেন। সবচেয়ে গুরুত্বপূর্ণ সেটিংটি হলো আপনার মডেল চালানোর জন্য আপনি কোন হার্ডওয়্যার অ্যাক্সিলারেটর ব্যবহার করতে চান তা নির্দিষ্ট করা।
  • Accelerator : এই enum-টি আপনাকে ইনফারেন্সের জন্য হার্ডওয়্যার বেছে নিতে দেয়। স্টার্টার প্রজেক্টটি এই অপশনগুলো পরিচালনা করার জন্য ইতিমধ্যেই কনফিগার করা আছে:
    • Accelerator.CPU : ডিভাইসের সিপিইউ-তে মডেলটি চালানোর জন্য। এটি সবচেয়ে সার্বজনীনভাবে সামঞ্জস্যপূর্ণ বিকল্প।
    • Accelerator.GPU : ডিভাইসের GPU-তে মডেলটি চালানোর জন্য। ইমেজ-ভিত্তিক মডেলের ক্ষেত্রে এটি প্রায়শই CPU-এর চেয়ে উল্লেখযোগ্যভাবে দ্রুততর হয়।
  • ইনপুট এবং আউটপুট বাফার ( TensorBuffer ) : LiteRT মডেলের ইনপুট এবং আউটপুটের জন্য TensorBuffer ব্যবহার করে। এটি আপনাকে মেমরির উপর সূক্ষ্ম নিয়ন্ত্রণ দেয় এবং অপ্রয়োজনীয় ডেটা কপি করা এড়ায়। আপনি আপনার CompiledModel ইনস্ট্যান্স থেকে সরাসরি model.createInputBuffers() এবং model.createOutputBuffers() ব্যবহার করে এই বাফারগুলো পাবেন এবং তারপর সেগুলোতে আপনার ইনপুট ডেটা লিখতে ও ফলাফল পড়তে পারবেন।
  • model.run() : এই ফাংশনটি ইনফারেন্স সম্পাদন করে। আপনি এতে ইনপুট এবং আউটপুট বাফারগুলো পাস করেন, এবং LiteRT নির্বাচিত হার্ডওয়্যার অ্যাক্সিলারেটরে মডেলটি চালানোর জটিল কাজটি সামলে নেয়।

৬. প্রাথমিক ImageSegmentationHelper বাস্তবায়ন শেষ করুন

এখন কোড লেখার পালা। আপনি ImageSegmentationHelper.kt এর প্রাথমিক বাস্তবায়ন সম্পন্ন করবেন। এর জন্য LiteRT মডেল ধারণ করার জন্য Segmenter প্রাইভেট ক্লাসটি সেট আপ করতে হবে এবং এটিকে সঠিকভাবে রিলিজ করার জন্য cleanup() ফাংশনটি ইমপ্লিমেন্ট করতে হবে।

  1. Segmenter ক্লাস এবং cleanup() ফাংশনটি সম্পূর্ণ করুন : ImageSegmentationHelper.kt ফাইলে, আপনি Segmenter নামের একটি প্রাইভেট ক্লাস এবং cleanup() নামের একটি ফাংশনের কাঠামো পাবেন। প্রথমে, মডেল ধারণ করার জন্য এর কনস্ট্রাক্টর সংজ্ঞায়িত করে, ইনপুট/আউটপুট বাফারের জন্য প্রোপার্টি তৈরি করে এবং মডেলটি রিলিজ করার জন্য একটি close() মেথড যোগ করে Segmenter ক্লাসটি সম্পূর্ণ করুন। তারপর, এই নতুন close() মেথডটিকে কল করার জন্য cleanup() ফাংশনটি ইমপ্লিমেন্ট করুন। বিদ্যমান Segmenter ক্লাস এবং cleanup() ফাংশনটিকে নিম্নলিখিত কোড দিয়ে প্রতিস্থাপন করুন: (~লাইন ৮৩)
    private class Segmenter(
        // Add this argument
        private val model: CompiledModel,
        private val coloredLabels: List<ColoredLabel>,
    ) {
        // Add these private vals
        private val inputBuffers = model.createInputBuffers()
        private val outputBuffers = model.createOutputBuffers()
    
        fun cleanup() {
          // cleanup buffers
          inputBuffers.forEach { it.close() }
          outputBuffers.forEach { it.close() }
          // cleanup model
          model.close()
        }
    }
    
  2. toAccelerator মেথডটি সংজ্ঞায়িত করুন : এই মেথডটি অ্যাক্সিলারেটর মেনু থেকে সংজ্ঞায়িত অ্যাক্সিলারেটর এনামগুলিকে ইম্পোর্ট করা LiteRT মডিউলগুলির জন্য নির্দিষ্ট অ্যাক্সিলারেটর এনামগুলির সাথে ম্যাপ করে (~লাইন ২২৫):
    fun toAccelerator(acceleratorEnum: AcceleratorEnum): Accelerator {
      return when (acceleratorEnum) {
        AcceleratorEnum.CPU -> Accelerator.CPU
        AcceleratorEnum.GPU -> Accelerator.GPU
      }
    }
    
  3. CompiledModel ইনিশিয়ালাইজ করুন : এখন initSegmenter ফাংশনটি খুঁজুন। এখানেই আপনি CompiledModel ইনস্ট্যান্সটি তৈরি করবেন এবং এটি ব্যবহার করে আপনার এখন-সংজ্ঞায়িত Segmenter ক্লাসটি ইনস্ট্যানশিয়েট করবেন। এই কোডটি নির্দিষ্ট অ্যাক্সিলারেটর (CPU বা GPU) দিয়ে মডেলটিকে সেট আপ করে এবং ইনফারেন্সের জন্য প্রস্তুত করে। initSegmenter এর TODO নিম্নলিখিত ইমপ্লিমেন্টেশন দিয়ে প্রতিস্থাপন করুন (Cmd/Ctrl+f 'initSegmenter` অথবা ~লাইন ৬২):
    cleanup()
    try {
      withContext(singleThreadDispatcher) {
        val model =
          CompiledModel.create(
            context.assets,
            "selfie_multiclass.tflite",
            CompiledModel.Options(toAccelerator(acceleratorEnum)),
            null,
          )
        segmenter = Segmenter(model, coloredLabels)
        Log.d(TAG, "Created an image segmenter")
      }
    } catch (e: Exception) {
      Log.i(TAG, "Create LiteRT from selfie_multiclass is failed: ${e.message}")
      _error.emit(e)
    }
    

৭. সেগমেন্টেশন এবং প্রিপ্রসেসিং শুরু করুন

এখন যেহেতু আমাদের একটি মডেল আছে, আমাদের সেগমেন্টেশন প্রক্রিয়াটি চালু করতে হবে এবং মডেলটির জন্য ইনপুট ডেটা প্রস্তুত করতে হবে।

ট্রিগার সেগমেন্টেশন

সেগমেন্টেশন প্রক্রিয়াটি MainViewModel.kt এ শুরু হয়, যা ক্যামেরা থেকে ফ্রেম গ্রহণ করে।

MainViewModel.kt খুলুন

  1. ক্যামেরা ফ্রেম থেকে সেগমেন্টেশন শুরু করুন : MainViewModel এর segment ফাংশনগুলো আমাদের সেগমেন্টেশন কাজের প্রবেশপথ। যখনই ক্যামেরা থেকে কোনো নতুন ছবি পাওয়া যায় বা গ্যালারি থেকে কোনো ছবি নির্বাচন করা হয়, তখনই এগুলো কল করা হয়। এরপর এই ফাংশনগুলো আমাদের ImageSegmentationHelper segment মেথডকে কল করে। উভয় segment ফাংশনের TODO গুলো নিম্নলিখিত কোড দিয়ে প্রতিস্থাপন করুন (লাইন ~১০৭):
    // For ImageProxy (from CameraX)
    fun segment(imageProxy: ImageProxy) {
        segmentJob =
            viewModelScope.launch {
                imageSegmentationHelper.segment(imageProxy.toBitmap(), imageProxy.imageInfo.rotationDegrees)
                imageProxy.close()
            }
    }
    
    // For Bitmaps (from gallery)
    fun segment(bitmap: Bitmap, rotationDegrees: Int) {
        segmentJob =
            viewModelScope.launch {
                val argbBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true)
                imageSegmentationHelper.segment(argbBitmap, rotationDegrees)
            }
    }
    

ছবিটি প্রাক-প্রক্রিয়া করুন

এবার ছবির প্রাক-প্রক্রিয়াকরণের কাজটি করার জন্য ImageSegmentationHelper.kt তে ফিরে যাওয়া যাক।

ImageSegmentationHelper.kt খুলুন

  1. পাবলিক segment ফাংশনটি ইমপ্লিমেন্ট করুন: এই ফাংশনটি একটি র‍্যাপার হিসেবে কাজ করে যা Segmenter ক্লাসের ভেতরের প্রাইভেট segment ফাংশনটিকে কল করে। TODO টি প্রতিস্থাপন করুন (~লাইন ৯৫):
    try {
      withContext(singleThreadDispatcher) {
        segmenter?.segment(bitmap, rotationDegrees)?.let { if (isActive) _segmentation.emit(it) }
      }
    } catch (e: Exception) {
      Log.i(TAG, "Image segment error occurred: ${e.message}")
      _error.emit(e)
    }
    
  2. প্রি-প্রসেসিং প্রয়োগ করুন : Segmenter ক্লাসের ভিতরে থাকা প্রাইভেট segment ফাংশনটিতে আমরা ইনপুট ইমেজকে মডেলের জন্য প্রস্তুত করতে প্রয়োজনীয় রূপান্তরগুলি সম্পাদন করব। এর মধ্যে রয়েছে ইমেজের স্কেলিং, রোটেশন এবং নর্মালাইজিং। এরপর এই ফাংশনটি ইনফারেন্স সম্পাদন করার জন্য আরেকটি প্রাইভেট segment ফাংশনকে কল করবে। segment(bitmap: Bitmap, ...) ফাংশনের TODO (~লাইন ১২১) দিয়ে প্রতিস্থাপন করুন:
    val totalStartTime = SystemClock.uptimeMillis()
    val rotation = -rotationDegrees / 90
    val (h, w) = Pair(256, 256)
    
    // Preprocessing
    val preprocessStartTime = SystemClock.uptimeMillis()
    var image = bitmap.scale(w, h, true)
    image = rot90Clockwise(image, rotation)
    val inputFloatArray = normalize(image, 127.5f, 127.5f)
    Log.d(TAG, "Preprocessing time: ${SystemClock.uptimeMillis() - preprocessStartTime} ms")
    
    // Inference
    val inferenceStartTime = SystemClock.uptimeMillis()
    val segmentResult = segment(inputFloatArray)
    Log.d(TAG, "Inference time: ${SystemClock.uptimeMillis() - inferenceStartTime} ms")
    
    Log.d(TAG, "Total segmentation time: ${SystemClock.uptimeMillis() - totalStartTime} ms")
    return SegmentationResult(segmentResult, SystemClock.uptimeMillis() - inferenceStartTime)
    

৮. LiteRT ব্যবহার করে প্রাথমিক অনুমান

ইনপুট ডেটা প্রিপ্রসেস করা হয়ে গেলে, আমরা এখন LiteRT ব্যবহার করে মূল ইনফারেন্সটি চালাতে পারি।

ImageSegmentationHelper.kt খুলুন

  1. মডেল এক্সিকিউশন বাস্তবায়ন করুন : private segment(inputFloatArray: FloatArray) ফাংশনটি হলো সেই জায়গা যেখানে আমরা সরাসরি LiteRT-এর run() মেথডের সাথে কাজ করি। আমরা আমাদের প্রি-প্রসেস করা ডেটা ইনপুট বাফারে লিখি, মডেলটি রান করি এবং আউটপুট বাফার থেকে ফলাফল পড়ি। এই ফাংশনের TODO (~লাইন ১৮৮) দিয়ে প্রতিস্থাপন করুন:
    val (h, w, c) = Triple(256, 256, 6)
    
    // MODEL EXECUTION PHASE
    val modelExecStartTime = SystemClock.uptimeMillis()
    
    // Write input data - measure time
    val bufferWriteStartTime = SystemClock.uptimeMillis()
    inputBuffers[0].writeFloat(inputFloatArray)
    val bufferWriteTime = SystemClock.uptimeMillis() - bufferWriteStartTime
    Log.d(TAG, "Buffer write time: $bufferWriteTime ms")
    
    // Optional tensor inspection
    logTensorStats("Input tensor", inputFloatArray)
    
    // Run model inference - measure time
    val modelRunStartTime = SystemClock.uptimeMillis()
    model.run(inputBuffers, outputBuffers)
    val modelRunTime = SystemClock.uptimeMillis() - modelRunStartTime
    Log.d(TAG, "Model.run() time: $modelRunTime ms")
    
    // Read output data - measure time
    val bufferReadStartTime = SystemClock.uptimeMillis()
    val outputFloatArray = outputBuffers[0].readFloat()
    val outputBuffer = FloatBuffer.wrap(outputFloatArray)
    val bufferReadTime = SystemClock.uptimeMillis() - bufferReadStartTime
    Log.d(TAG, "Buffer read time: $bufferReadTime ms")
    
    val modelExecTime = SystemClock.uptimeMillis() - modelExecStartTime
    Log.d(TAG, "Total model execution time: $modelExecTime ms")
    
    // Optional tensor inspection
    logTensorStats("Output tensor", outputFloatArray)
    
    // POSTPROCESSING PHASE
    val postprocessStartTime = SystemClock.uptimeMillis()
    
    // Process mask from model output
    val inferenceData = InferenceData(width = w, height = h, channels = c, buffer = outputBuffer)
    val mask = processImage(inferenceData)
    
    val postprocessTime = SystemClock.uptimeMillis() - postprocessStartTime
    Log.d(TAG, "Postprocessing time (mask creation): $postprocessTime ms")
    
    return Segmentation(
      listOf(Mask(mask, inferenceData.width, inferenceData.height)),
      coloredLabels,
    )
    

৯. পোস্ট-প্রসেসিং এবং ওভারলে প্রদর্শন

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

ImageSegmentationHelper.kt খুলুন

  1. আউটপুট প্রসেসিং বাস্তবায়ন করুন : processImage ফাংশনটি মডেল থেকে প্রাপ্ত কাঁচা ফ্লোটিং-পয়েন্ট আউটপুটকে একটি ByteBuffer এ রূপান্তর করে, যা সেগমেন্টেশন মাস্ককে উপস্থাপন করে। এটি প্রতিটি পিক্সেলের জন্য সর্বোচ্চ সম্ভাবনাসম্পন্ন ক্লাসটি খুঁজে বের করার মাধ্যমে এই কাজটি করে। এর TODO প্রতিস্থাপন করুন (~লাইন ২৩৮):
    val mask = ByteBuffer.allocateDirect(inferenceData.width * inferenceData.height)
    for (i in 0 until inferenceData.height) {
        for (j in 0 until inferenceData.width) {
            val offset = inferenceData.channels * (i * inferenceData.width + j)
    
            var maxIndex = 0
            var maxValue = inferenceData.buffer.get(offset)
    
            for (index in 1 until inferenceData.channels) {
                if (inferenceData.buffer.get(offset + index) > maxValue) {
                    maxValue = inferenceData.buffer.get(offset + index)
                    maxIndex = index
                }
            }
            mask.put(i * inferenceData.width + j, maxIndex.toByte())
        }
    }
    return mask
    

MainViewModel.kt খুলুন

  1. সেগমেন্টেশন ফলাফল সংগ্রহ এবং প্রক্রিয়াকরণ : এখন আমরা ImageSegmentationHelper থেকে প্রাপ্ত সেগমেন্টেশন ফলাফল প্রক্রিয়াকরণ করতে MainViewModel এ ফিরে যাব। segmentationUiShareFlow ফাংশনটি SegmentationResult সংগ্রহ করে, মাস্কটিকে একটি রঙিন Bitmap রূপান্তর করে এবং UI-তে সরবরাহ করে। segmentationUiShareFlow প্রপার্টির TODO (~লাইন ৬৩) এই কোড দিয়ে প্রতিস্থাপন করুন – আগে থেকে থাকা কোডটি প্রতিস্থাপন করবেন না, শুধু বডিটি পূরণ করুন:
    viewModelScope.launch {
      imageSegmentationHelper.segmentation
        .filter { it.segmentation.masks.isNotEmpty() }
        .map {
          val segmentation = it.segmentation
          val mask = segmentation.masks[0]
          val maskArray = mask.data
          val width = mask.width
          val height = mask.height
          val pixelSize = width * height
          val pixels = IntArray(pixelSize)
    
          val colorLabels =
            segmentation.coloredLabels.mapIndexed { index, coloredLabel ->
              ColorLabel(index, coloredLabel.label, coloredLabel.argb)
            }
          // Set color for pixels
          for (i in 0 until pixelSize) {
            val colorLabel = colorLabels[maskArray[i].toInt()]
            val color = colorLabel.getColor()
            pixels[i] = color
          }
          // Get image info
          val overlayInfo = OverlayInfo(pixels = pixels, width = width, height = height)
    
          val inferenceTime = it.inferenceTime
          Pair(overlayInfo, inferenceTime)
        }
        .collect { flow.emit(it) }
    }
    

ওপেন view/SegmentationOverlay.kt

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

  1. ওভারলে ওরিয়েন্টেশন পরিচালনা করুন : SegmentationOverlay.kt ফাইলে TODO খুঁজুন এবং এটিকে নিম্নলিখিত কোড দিয়ে প্রতিস্থাপন করুন। এই কোডটি পরীক্ষা করে দেখে যে সামনের ক্যামেরাটি সক্রিয় আছে কিনা এবং যদি থাকে, তবে Canvas আঁকার আগে ওভারলে Bitmap একটি হরাইজন্টাল ফ্লিপ প্রয়োগ করে। (~লাইন ৪২):
    val orientedBitmap =
      if (lensFacing == CameraSelector.LENS_FACING_FRONT) {
        // Create a matrix for horizontal flipping
        val matrix = Matrix().apply { preScale(-1f, 1f) }
        Bitmap.createBitmap(image, 0, 0, image.width, image.height, matrix, false).also {
          image.recycle()
        }
      } else {
        image
      }
    

১০. চূড়ান্ত অ্যাপটি চালান এবং ব্যবহার করুন

আপনি এখন সমস্ত প্রয়োজনীয় কোড পরিবর্তন সম্পন্ন করেছেন। এবার অ্যাপটি চালিয়ে আপনার কাজটি বাস্তবে দেখতে হবে!

  1. অ্যাপটি চালান : আপনার অ্যান্ড্রয়েড ডিভাইসটি সংযুক্ত করুন এবং অ্যান্ড্রয়েড স্টুডিও টুলবারে থাকা 'রান' (Run) বোতামে ক্লিক করুন।

রান বাটন

  1. ফিচারগুলো পরীক্ষা করুন : অ্যাপটি চালু হলে, আপনি একটি রঙিন সেগমেন্টেশন ওভারলে সহ লাইভ ক্যামেরা ফিড দেখতে পাবেন।
    • ক্যামেরা পরিবর্তন করুন : সামনের ও পেছনের ক্যামেরার মধ্যে পরিবর্তন করতে উপরের ক্যামেরা ফ্লিপ আইকনটিতে ট্যাপ করুন। লক্ষ্য করুন, ওভারলেটি কীভাবে সঠিকভাবে বিন্যস্ত হয়ে যায়।
    • অ্যাক্সিলারেটর পরিবর্তন করুন : হার্ডওয়্যার অ্যাক্সিলারেটর পরিবর্তন করতে নিচের 'CPU' বা 'GPU' বোতামে ট্যাপ করুন। স্ক্রিনের নিচে প্রদর্শিত ইনফারেন্স টাইমের পরিবর্তনটি লক্ষ্য করুন। GPU উল্লেখযোগ্যভাবে দ্রুততর হওয়া উচিত।
    • গ্যালারির ছবি ব্যবহার করুন : আপনার ডিভাইসের ফটো গ্যালারি থেকে একটি ছবি বেছে নিতে উপরের 'গ্যালারি' ট্যাবে ট্যাপ করুন। অ্যাপটি নির্বাচিত স্থির ছবিটির উপর সেগমেন্টেশন চালাবে।

অন্যান্য UI

এখন আপনার কাছে LiteRT দ্বারা চালিত একটি সম্পূর্ণ কার্যকরী, রিয়েল-টাইম ইমেজ সেগমেন্টেশন অ্যাপ রয়েছে!

১১. উন্নত (ঐচ্ছিক): এনপিইউ ব্যবহার করা

এই রিপোজিটরিতে অ্যাপটির একটি সংস্করণও রয়েছে যা নিউরাল প্রসেসিং ইউনিট (NPU)-এর জন্য অপ্টিমাইজ করা হয়েছে। যেসব ডিভাইসে সামঞ্জস্যপূর্ণ NPU রয়েছে, সেগুলিতে এই NPU সংস্করণটি পারফরম্যান্সে উল্লেখযোগ্য উন্নতি ঘটাতে পারে।

NPU সংস্করণটি ব্যবহার করে দেখতে, Android Studio-তে kotlin_npu/android প্রজেক্টটি খুলুন। এর কোডটি CPU/GPU সংস্করণের মতোই এবং এটি NPU ডেলিগেট ব্যবহার করার জন্য কনফিগার করা হয়েছে।

NPU ডেলিগেট বাস্তবায়নের একটি উদাহরণ দেখতে আমাদের নমুনা অ্যাপটি দেখুন।

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

আপনি LiteRT ব্যবহার করে রিয়েল-টাইম ইমেজ সেগমেন্টেশন সম্পাদনকারী একটি অ্যান্ড্রয়েড অ্যাপ সফলভাবে তৈরি করেছেন। আপনি শিখেছেন কীভাবে:

  • একটি অ্যান্ড্রয়েড অ্যাপে LiteRT রানটাইম সংহত করুন।
  • একটি TFLite ইমেজ সেগমেন্টেশন মডেল লোড করে চালান।
  • মডেলের ইনপুট প্রাক-প্রক্রিয়াজাত করুন।
  • সেগমেন্টেশন মাস্ক তৈরি করার জন্য মডেলের আউটপুটটি প্রসেস করুন।
  • রিয়েল-টাইম ক্যামেরা অ্যাপের জন্য CameraX ব্যবহার করুন।

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

  • অন্য একটি ইমেজ সেগমেন্টেশন মডেল ব্যবহার করে দেখুন।
  • বিভিন্ন LiteRT ডেলিগেট (CPU, GPU, NPU) নিয়ে পরীক্ষা করুন।

আরও জানুন