পাইথন ব্যবহার করে স্ক্র্যাচ থেকে ভিডিও কমপ্রেশন অ্যালগরিদম বোঝা এবং প্রয়োগ করার একটি বিস্তারিত নির্দেশিকা। আধুনিক ভিডিও কোডেকের পেছনের তত্ত্ব এবং অনুশীলন শিখুন।
পাইথনে ভিডিও কোডেক তৈরি: কমপ্রেশন অ্যালগরিদমের গভীরে একটি অনুসন্ধান
আমাদের এই হাইপার-কানেক্টেড বিশ্বে, ভিডিওই রাজা। স্ট্রিমিং সার্ভিস এবং ভিডিও কনফারেন্সিং থেকে শুরু করে সোশ্যাল মিডিয়া ফিড পর্যন্ত, ডিজিটাল ভিডিও ইন্টারনেট ট্র্যাফিকের বেশিরভাগ অংশ জুড়ে রয়েছে। কিন্তু একটি স্ট্যান্ডার্ড ইন্টারনেট সংযোগের মাধ্যমে একটি হাই-ডেফিনিশন মুভি পাঠানো কীভাবে সম্ভব? এর উত্তরটি একটি আকর্ষণীয় এবং জটিল ক্ষেত্রের মধ্যে নিহিত: ভিডিও কমপ্রেশন। এই প্রযুক্তির কেন্দ্রবিন্দুতে রয়েছে ভিডিও কোডেক (COder-DECoder), যা ফাইলের আকার মারাত্মকভাবে কমাতে এবং একই সাথে ছবির মান বজায় রাখার জন্য ডিজাইন করা একটি অত্যাধুনিক অ্যালগরিদম সেট।
যদিও H.264, HEVC (H.265), এবং রয়্যালটি-মুক্ত AV1-এর মতো ইন্ডাস্ট্রি-স্ট্যান্ডার্ড কোডেকগুলি অবিশ্বাস্যভাবে জটিল ইঞ্জিনিয়ারিংয়ের ফসল, তবে তাদের মৌলিক নীতিগুলি বোঝা যেকোনো অনুপ্রাণিত ডেভেলপারের জন্য সম্ভব। এই নির্দেশিকা আপনাকে ভিডিও কমপ্রেশনের গভীর জগতে নিয়ে যাবে। আমরা কেবল তত্ত্ব নিয়ে আলোচনা করব না; আমরা পাইথন ব্যবহার করে স্ক্র্যাচ থেকে একটি সরলীকৃত, শিক্ষামূলক ভিডিও কোডেক তৈরি করব। এই হাতে-কলমে পদ্ধতিটি সেইসব চমৎকার ধারণাগুলি উপলব্ধি করার সেরা উপায় যা আধুনিক ভিডিও স্ট্রিমিংকে সম্ভব করে তুলেছে।
পাইথন কেন? যদিও এটি রিয়েল-টাইম, হাই-পারফরম্যান্স বাণিজ্যিক কোডেকের জন্য ব্যবহৃত ভাষা নয় (যা সাধারণত C/C++ বা এমনকি অ্যাসেম্বলিতে লেখা হয়), পাইথনের পঠনযোগ্যতা এবং এর শক্তিশালী লাইব্রেরি যেমন NumPy, SciPy এবং OpenCV এটিকে শেখা, প্রোটোটাইপিং এবং গবেষণার জন্য নিখুঁত পরিবেশে পরিণত করে। আপনি লো-লেভেল মেমরি ম্যানেজমেন্টে আটকে না গিয়ে অ্যালগরিদমগুলিতে মনোযোগ দিতে পারবেন।
ভিডিও কমপ্রেশনের মূল ধারণাগুলি বোঝা
এক লাইন কোড লেখার আগে, আমাদের বুঝতে হবে আমরা কী অর্জন করতে চাইছি। ভিডিও কমপ্রেশনের লক্ষ্য হলো অপ্রয়োজনীয় ডেটা حذف করা। একটি কাঁচা, আনকম্প্রেসড ভিডিও বিশাল আকারের হয়। ৩০ ফ্রেম প্রতি সেকেন্ডে ১০৮০পি ভিডিওর মাত্র এক মিনিটের আকার ৭ জিবি ছাড়িয়ে যেতে পারে। এই ডেটা দানবকে নিয়ন্ত্রণ করতে, আমরা প্রধানত দুই ধরনের পুনরাবৃত্তি (redundancy) ব্যবহার করি।
কমপ্রেশনের দুটি স্তম্ভ: স্থানিক এবং সময়গত পুনরাবৃত্তি
- স্থানিক (ইন্ট্রা-ফ্রেম) পুনরাবৃত্তি: এটি হলো একটি একক ফ্রেমের মধ্যে থাকা পুনরাবৃত্তি। একটি বিশাল নীল আকাশ বা সাদা দেয়ালের কথা ভাবুন। সেই এলাকার প্রতিটি পিক্সেলের জন্য রঙের মান সংরক্ষণ করার পরিবর্তে, আমরা এটিকে আরও দক্ষতার সাথে বর্ণনা করতে পারি। এটি JPEG-এর মতো ইমেজ কমপ্রেশন ফরম্যাটের পেছনের একই নীতি।
- সময়গত (ইন্টার-ফ্রেম) পুনরাবৃত্তি: এটি হলো পরপর দুটি ফ্রেমের মধ্যে থাকা পুনরাবৃত্তি। বেশিরভাগ ভিডিওতে, একটি ফ্রেম থেকে অন্য ফ্রেমে দৃশ্যটি সম্পূর্ণরূপে পরিবর্তিত হয় না। উদাহরণস্বরূপ, একটি স্থির পটভূমির সামনে কথা বলা একজন ব্যক্তির ক্ষেত্রে প্রচুর পরিমাণে সময়গত পুনরাবৃত্তি থাকে। পটভূমি একই থাকে; ছবির কেবল একটি ছোট অংশ (ব্যক্তির মুখ এবং শরীর) নড়াচড়া করে। ভিডিওতে এটিই কমপ্রেশনের সবচেয়ে গুরুত্বপূর্ণ উৎস।
মূল ফ্রেমের প্রকারভেদ: I-ফ্রেম, P-ফ্রেম, এবং B-ফ্রেম
সময়গত পুনরাবৃত্তি কাজে লাগানোর জন্য, কোডেকগুলি প্রতিটি ফ্রেমকে সমানভাবে বিবেচনা করে না। তারা ফ্রেমগুলোকে বিভিন্ন প্রকারে শ্রেণীবদ্ধ করে, যা একটি গ্রুপ অফ পিকচার্স (GOP) নামক একটি ক্রম তৈরি করে।
- I-ফ্রেম (ইন্ট্রা-কোডেড ফ্রেম): একটি I-ফ্রেম হলো একটি সম্পূর্ণ, স্বয়ংসম্পূর্ণ ছবি। এটি শুধুমাত্র স্থানিক পুনরাবৃত্তি ব্যবহার করে সংকুচিত করা হয়, অনেকটা JPEG-এর মতো। I-ফ্রেমগুলি ভিডিও স্ট্রিমে অ্যাঙ্কর পয়েন্ট হিসাবে কাজ করে, যা একজন দর্শককে প্লেব্যাক শুরু করতে বা একটি নতুন অবস্থানে যেতে দেয়। এগুলি সবচেয়ে বড় আকারের ফ্রেম, কিন্তু ভিডিও পুনরুৎপাদনের জন্য অপরিহার্য।
- P-ফ্রেম (প্রেডিকটেড ফ্রেম): একটি P-ফ্রেম পূর্ববর্তী I-ফ্রেম বা P-ফ্রেম দেখে এনকোড করা হয়। পুরো ছবিটি সংরক্ষণ করার পরিবর্তে, এটি কেবল পার্থক্যগুলি সংরক্ষণ করে। উদাহরণস্বরূপ, এটি এমন নির্দেশাবলী সংরক্ষণ করে যেমন "শেষ ফ্রেম থেকে এই পিক্সেলের ব্লকটি নাও, এটিকে ৫ পিক্সেল ডানদিকে সরাও, এবং এখানে রঙের সামান্য পরিবর্তনগুলি রয়েছে।" এটি মোশন এস্টিমেশন নামক একটি প্রক্রিয়ার মাধ্যমে করা হয়।
- B-ফ্রেম (বাই-ডিরেকশনালি প্রেডিকটেড ফ্রেম): একটি B-ফ্রেম সবচেয়ে কার্যকর। এটি পূর্বাভাসের জন্য পূর্ববর্তী এবং পরবর্তী উভয় ফ্রেমকে রেফারেন্স হিসাবে ব্যবহার করতে পারে। এটি এমন দৃশ্যের জন্য উপযোগী যেখানে একটি বস্তু সাময়িকভাবে লুকিয়ে যায় এবং তারপর আবার আবির্ভূত হয়। সামনে এবং পিছনে তাকিয়ে, কোডেক একটি আরও সঠিক এবং ডেটা-সাশ্রয়ী পূর্বাভাস তৈরি করতে পারে। তবে, ভবিষ্যতের ফ্রেম ব্যবহার করার ফলে একটি ছোট বিলম্ব (latency) হয়, যা ভিডিও কলের মতো রিয়েল-টাইম অ্যাপ্লিকেশনগুলির জন্য এগুলিকে কম উপযুক্ত করে তোলে।
একটি সাধারণ GOP দেখতে এমন হতে পারে: I B B P B B P B B I ...। এনকোডার কমপ্রেশন দক্ষতা এবং সিক করার ক্ষমতার মধ্যে ভারসাম্য বজায় রাখার জন্য ফ্রেমের সর্বোত্তম প্যাটার্ন নির্ধারণ করে।
কমপ্রেশন পাইপলাইন: একটি ধাপে ধাপে বিশ্লেষণ
আধুনিক ভিডিও এনকোডিং একটি বহু-স্তরীয় পাইপলাইন। প্রতিটি পর্যায় ডেটাকে আরও সংকোচনযোগ্য করার জন্য রূপান্তরিত করে। আসুন একটি একক ফ্রেম এনকোড করার মূল ধাপগুলি দেখে নেওয়া যাক।

ধাপ ১: কালার স্পেস কনভার্সন (RGB থেকে YCbCr)
বেশিরভাগ ভিডিও RGB (লাল, সবুজ, নীল) কালার স্পেসে শুরু হয়। তবে, মানুষের চোখ রঙের (ক্রোমা) পরিবর্তনের চেয়ে উজ্জ্বলতার (লুমা) পরিবর্তনে অনেক বেশি সংবেদনশীল। কোডেকগুলি RGB-কে YCbCr-এর মতো লুমা/ক্রোমা ফরম্যাটে রূপান্তর করে এর সুবিধা নেয়।
- Y: লুমা উপাদান (উজ্জ্বলতা)।
- Cb: ব্লু-ডিফারেন্স ক্রোমা উপাদান।
- Cr: রেড-ডিফারেন্স ক্রোমা উপাদান।
উজ্জ্বলতা থেকে রঙকে আলাদা করে, আমরা ক্রোমা সাবস্যাম্পলিং প্রয়োগ করতে পারি। এই কৌশলটি রঙের চ্যানেলগুলির (Cb এবং Cr) রেজোলিউশন কমিয়ে দেয় এবং উজ্জ্বলতা চ্যানেলের (Y) জন্য সম্পূর্ণ রেজোলিউশন বজায় রাখে, যার প্রতি আমাদের চোখ সবচেয়ে বেশি সংবেদনশীল। একটি সাধারণ স্কিম হলো ৪:২:০, যা প্রায় কোনো লক্ষণীয় মানের ক্ষতি ছাড়াই ৭৫% রঙের তথ্য বাতিল করে, যার ফলে তাৎক্ষণিক কমপ্রেশন অর্জিত হয়।
ধাপ ২: ফ্রেম পার্টিশনিং (ম্যাক্রোব্লক)
এনকোডার একবারে পুরো ফ্রেম প্রক্রিয়া করে না। এটি ফ্রেমটিকে ছোট ছোট ব্লকে ভাগ করে, সাধারণত ১৬x১৬ বা ৮x৮ পিক্সেলের, যেগুলিকে ম্যাক্রোব্লক বলা হয়। পরবর্তী সমস্ত প্রক্রিয়াকরণের ধাপগুলি (প্রেডিকশন, ট্রান্সফর্ম, ইত্যাদি) ব্লক-বাই-ব্লক ভিত্তিতে সঞ্চালিত হয়।
ধাপ ৩: প্রেডিকশন (ইন্টার এবং ইন্ট্রা)
এখানেই আসল জাদুটি ঘটে। প্রতিটি ম্যাক্রোব্লকের জন্য, এনকোডার সিদ্ধান্ত নেয় যে ইন্ট্রা-ফ্রেম না ইন্টার-ফ্রেম প্রেডিকশন ব্যবহার করবে।
- I-ফ্রেমের জন্য (ইন্ট্রা-প্রেডিকশন): এনকোডার বর্তমান ব্লকটিকে একই ফ্রেমের মধ্যে তার ইতিমধ্যে এনকোড করা প্রতিবেশীদের (উপরের এবং বাম দিকের ব্লক) পিক্সেলের উপর ভিত্তি করে পূর্বাভাস দেয়। এরপর এটিকে শুধুমাত্র পূর্বাভাস এবং আসল ব্লকের মধ্যেকার ক্ষুদ্র পার্থক্য (রেসিড্যুয়াল) এনকোড করতে হয়।
- P-ফ্রেম বা B-ফ্রেমের জন্য (ইন্টার-প্রেডিকশন): এটি হলো মোশন এস্টিমেশন। এনকোডার একটি রেফারেন্স ফ্রেমে একটি মিলে যাওয়া ব্লক অনুসন্ধান করে। যখন এটি সেরা ম্যাচ খুঁজে পায়, তখন এটি একটি মোশন ভেক্টর রেকর্ড করে (যেমন, "১০ পিক্সেল ডানে, ২ পিক্সেল নিচে সরান") এবং রেসিড্যুয়াল গণনা করে। প্রায়শই, রেসিড্যুয়াল শূন্যের কাছাকাছি থাকে, যা এনকোড করতে খুব কম বিট প্রয়োজন হয়।
ধাপ ৪: ট্রান্সফরমেশন (যেমন, ডিসক্রিট কোসাইন ট্রান্সফর্ম - DCT)
প্রেডিকশনের পরে, আমরা একটি রেসিড্যুয়াল ব্লক পাই। এই ব্লকটি ডিসক্রিট কোসাইন ট্রান্সফর্ম (DCT)-এর মতো একটি গাণিতিক রূপান্তরের মধ্য দিয়ে চালনা করা হয়। DCT নিজে ডেটা সংকুচিত করে না, তবে এটি ডেটা উপস্থাপনের পদ্ধতিকে মৌলিকভাবে পরিবর্তন করে। এটি স্থানিক পিক্সেল মানগুলিকে ফ্রিকোয়েন্সি কোএফিসিয়েন্টে রূপান্তর করে। DCT-এর জাদু হলো যে বেশিরভাগ প্রাকৃতিক ছবির জন্য, এটি চাক্ষুষ শক্তির বেশিরভাগ অংশ ব্লকের উপরের-বাম কোণায় কয়েকটি কোএফিসিয়েন্টে (নিম্ন-ফ্রিকোয়েন্সি উপাদান) কেন্দ্রীভূত করে, যখন বাকি কোএফিসিয়েন্টগুলো (উচ্চ-ফ্রিকোয়েন্সি নয়েজ) শূন্যের কাছাকাছি থাকে।
ধাপ ৫: কোয়ান্টাইজেশন
এটি পাইপলাইনের প্রধান lossy (ক্ষতিকর) ধাপ এবং কোয়ালিটি বনাম বিটরেট ট্রেড-অফ নিয়ন্ত্রণের চাবিকাঠি। DCT কোএফিসিয়েন্টের রূপান্তরিত ব্লকটিকে একটি কোয়ান্টাইজেশন ম্যাট্রিক্স দ্বারা ভাগ করা হয়, এবং ফলাফলগুলিকে নিকটতম পূর্ণসংখ্যায় রাউন্ড করা হয়। কোয়ান্টাইজেশন ম্যাট্রিক্সে উচ্চ-ফ্রিকোয়েন্সি কোএফিসিয়েন্টগুলোর জন্য বড় মান থাকে, যা কার্যকরভাবে তাদের অনেককে শূন্যে নামিয়ে আনে। এখানেই বিপুল পরিমাণ ডেটা বাতিল করা হয়। একটি উচ্চতর কোয়ান্টাইজেশন প্যারামিটার আরও বেশি শূন্য, উচ্চতর কমপ্রেশন এবং নিম্নমানের চাক্ষুষ গুণমান (প্রায়শই ব্লকি আর্টিফ্যাক্ট হিসাবে দেখা যায়) নিয়ে আসে।
ধাপ ৬: এনট্রপি কোডিং
চূড়ান্ত পর্যায়টি একটি লসলেস কমপ্রেশন ধাপ। কোয়ান্টাইজড কোএফিসিয়েন্ট, মোশন ভেক্টর এবং অন্যান্য মেটাডেটা স্ক্যান করে একটি বাইনারি স্ট্রিমে রূপান্তরিত করা হয়। রান-লেংথ এনকোডিং (RLE) এবং হাফম্যান কোডিং বা CABAC (কনটেক্সট-অ্যাডাপটিভ বাইনারি অ্যারিথমেটিক কোডিং)-এর মতো আরও উন্নত পদ্ধতি ব্যবহার করা হয়। এই অ্যালগরিদমগুলি ঘন ঘন ব্যবহৃত প্রতীকগুলির (যেমন কোয়ান্টাইজেশনের দ্বারা সৃষ্ট অনেক শূন্য) জন্য ছোট কোড এবং কম ব্যবহৃত প্রতীকগুলির জন্য দীর্ঘ কোড বরাদ্দ করে, ডেটা স্ট্রিম থেকে চূড়ান্ত বিটগুলি বের করে আনে।
ডিকোডার সহজভাবে এই ধাপগুলি বিপরীত ক্রমে সম্পাদন করে: এনট্রপি ডিকোডিং -> ইনভার্স কোয়ান্টাইজেশন -> ইনভার্স ট্রান্সফর্ম -> মোশন কমপেনসেশন -> ফ্রেম পুনর্গঠন।
পাইথনে একটি সরলীকৃত ভিডিও কোডেক প্রয়োগ করা
এবার, তত্ত্বকে বাস্তবে প্রয়োগ করা যাক। আমরা একটি শিক্ষামূলক কোডেক তৈরি করব যা I-ফ্রেম এবং P-ফ্রেম ব্যবহার করে। এটি মূল পাইপলাইনটি প্রদর্শন করবে: মোশন এস্টিমেশন, DCT, কোয়ান্টাইজেশন এবং সংশ্লিষ্ট ডিকোডিং ধাপসমূহ।
দাবিত্যাগ: এটি শেখার জন্য ডিজাইন করা একটি *খেলনা* কোডেক। এটি অপ্টিমাইজ করা হয়নি এবং H.264-এর সাথে তুলনীয় ফলাফল দেবে না। আমাদের লক্ষ্য হলো অ্যালগরিদমগুলিকে কার্যকরভাবে দেখা।
পূর্বশর্ত
আপনার নিম্নলিখিত পাইথন লাইব্রেরিগুলির প্রয়োজন হবে। আপনি pip ব্যবহার করে সেগুলি ইনস্টল করতে পারেন:
pip install numpy opencv-python scipy
প্রজেক্টের কাঠামো
আসুন আমাদের কোড কয়েকটি ফাইলে সংগঠিত করি:
main.py: এনকোডিং এবং ডিকোডিং প্রক্রিয়া চালানোর জন্য প্রধান স্ক্রিপ্ট।encoder.py: এনকোডারের জন্য যুক্তি রয়েছে।decoder.py: ডিকোডারের জন্য যুক্তি রয়েছে।utils.py: ভিডিও I/O এবং রূপান্তরের জন্য সহায়ক ফাংশন।
পর্ব ১: মূল ইউটিলিটি (`utils.py`)
আমরা DCT, কোয়ান্টাইজেশন এবং তাদের ইনভার্স ফাংশনগুলির জন্য সহায়ক ফাংশন দিয়ে শুরু করব। আমাদের একটি ফ্রেমকে ব্লকে বিভক্ত করার জন্য একটি ফাংশনও প্রয়োজন হবে।
# utils.py
import numpy as np
from scipy.fftpack import dct, idct
BLOCK_SIZE = 8
# A standard JPEG quantization matrix (scaled for our purposes)
QUANTIZATION_MATRIX = np.array([
[16, 11, 10, 16, 24, 40, 51, 61],
[12, 12, 14, 19, 26, 58, 60, 55],
[14, 13, 16, 24, 40, 57, 69, 56],
[14, 17, 22, 29, 51, 87, 80, 62],
[18, 22, 37, 56, 68, 109, 103, 77],
[24, 35, 55, 64, 81, 104, 113, 92],
[49, 64, 78, 87, 103, 121, 120, 101],
[72, 92, 95, 98, 112, 100, 103, 99]
])
def apply_dct(block):
"""Applies 2D DCT to a block."""
# Center the pixel values around 0
block = block - 128
return dct(dct(block.T, norm='ortho').T, norm='ortho')
def apply_idct(dct_block):
"""Applies 2D Inverse DCT to a block."""
block = idct(idct(dct_block.T, norm='ortho').T, norm='ortho')
# De-center and clip to valid pixel range
return np.round(block + 128).clip(0, 255)
def quantize(dct_block, qp=1):
"""Quantizes a DCT block. qp is a quality parameter."""
return np.round(dct_block / (QUANTIZATION_MATRIX * qp)).astype(int)
def dequantize(quantized_block, qp=1):
"""Dequantizes a block."""
return quantized_block * (QUANTIZATION_MATRIX * qp)
def frame_to_blocks(frame):
"""Splits a frame into 8x8 blocks."""
blocks = []
h, w = frame.shape
for i in range(0, h, BLOCK_SIZE):
for j in range(0, w, BLOCK_SIZE):
blocks.append(frame[i:i+BLOCK_SIZE, j:j+BLOCK_SIZE])
return blocks
def blocks_to_frame(blocks, h, w):
"""Reconstructs a frame from 8x8 blocks."""
frame = np.zeros((h, w), dtype=np.uint8)
k = 0
for i in range(0, h, BLOCK_SIZE):
for j in range(0, w, BLOCK_SIZE):
frame[i:i+BLOCK_SIZE, j:j+BLOCK_SIZE] = blocks[k]
k += 1
return frame
পর্ব ২: এনকোডার (`encoder.py`)
এনকোডার হলো সবচেয়ে জটিল অংশ। আমরা মোশন এস্টিমেশনের জন্য একটি সহজ ব্লক-ম্যাচিং অ্যালগরিদম প্রয়োগ করব এবং তারপর I-ফ্রেম ও P-ফ্রেম প্রক্রিয়া করব।
# encoder.py
import numpy as np
from utils import apply_dct, quantize, frame_to_blocks, BLOCK_SIZE
def get_motion_vectors(current_frame, reference_frame, search_range=8):
"""A simple block matching algorithm for motion estimation."""
h, w = current_frame.shape
motion_vectors = []
for i in range(0, h, BLOCK_SIZE):
for j in range(0, w, BLOCK_SIZE):
current_block = current_frame[i:i+BLOCK_SIZE, j:j+BLOCK_SIZE]
best_match_sad = float('inf')
best_match_vector = (0, 0)
# Search in the reference frame
for y in range(-search_range, search_range + 1):
for x in range(-search_range, search_range + 1):
ref_i, ref_j = i + y, j + x
if 0 <= ref_i <= h - BLOCK_SIZE and 0 <= ref_j <= w - BLOCK_SIZE:
ref_block = reference_frame[ref_i:ref_i+BLOCK_SIZE, ref_j:ref_j+BLOCK_SIZE]
sad = np.sum(np.abs(current_block - ref_block))
if sad < best_match_sad:
best_match_sad = sad
best_match_vector = (y, x)
motion_vectors.append(best_match_vector)
return motion_vectors
def encode_iframe(frame, qp=1):
"""Encodes an I-frame."""
h, w = frame.shape
blocks = frame_to_blocks(frame)
quantized_blocks = []
for block in blocks:
dct_block = apply_dct(block.astype(float))
quantized_block = quantize(dct_block, qp)
quantized_blocks.append(quantized_block)
return {'type': 'I', 'h': h, 'w': w, 'data': quantized_blocks, 'qp': qp}
def encode_pframe(current_frame, reference_frame, qp=1):
"""Encodes a P-frame."""
h, w = current_frame.shape
motion_vectors = get_motion_vectors(current_frame, reference_frame)
quantized_residuals = []
k = 0
for i in range(0, h, BLOCK_SIZE):
for j in range(0, w, BLOCK_SIZE):
current_block = current_frame[i:i+BLOCK_SIZE, j:j+BLOCK_SIZE]
mv_y, mv_x = motion_vectors[k]
ref_block = reference_frame[i+mv_y : i+mv_y+BLOCK_SIZE, j+mv_x : j+mv_x+BLOCK_SIZE]
residual = current_block.astype(float) - ref_block.astype(float)
dct_residual = apply_dct(residual)
quantized_residual = quantize(dct_residual, qp)
quantized_residuals.append(quantized_residual)
k += 1
return {'type': 'P', 'motion_vectors': motion_vectors, 'data': quantized_residuals, 'qp': qp}
পর্ব ৩: ডিকোডার (`decoder.py`)
ডিকোডার প্রক্রিয়াটিকে বিপরীত করে। P-ফ্রেমের জন্য, এটি সংরক্ষিত মোশন ভেক্টর ব্যবহার করে মোশন কমপেনসেশন সম্পাদন করে।
# decoder.py
import numpy as np
from utils import apply_idct, dequantize, blocks_to_frame, BLOCK_SIZE
def decode_iframe(encoded_frame):
"""Decodes an I-frame."""
h, w = encoded_frame['h'], encoded_frame['w']
qp = encoded_frame['qp']
quantized_blocks = encoded_frame['data']
reconstructed_blocks = []
for q_block in quantized_blocks:
dct_block = dequantize(q_block, qp)
block = apply_idct(dct_block)
reconstructed_blocks.append(block.astype(np.uint8))
return blocks_to_frame(reconstructed_blocks, h, w)
def decode_pframe(encoded_frame, reference_frame):
"""Decodes a P-frame using its reference frame."""
h, w = reference_frame.shape
qp = encoded_frame['qp']
motion_vectors = encoded_frame['motion_vectors']
quantized_residuals = encoded_frame['data']
reconstructed_blocks = []
k = 0
for i in range(0, h, BLOCK_SIZE):
for j in range(0, w, BLOCK_SIZE):
# Decode the residual
dct_residual = dequantize(quantized_residuals[k], qp)
residual = apply_idct(dct_residual)
# Perform motion compensation
mv_y, mv_x = motion_vectors[k]
ref_block = reference_frame[i+mv_y : i+mv_y+BLOCK_SIZE, j+mv_x : j+mv_x+BLOCK_SIZE]
# Reconstruct the block
reconstructed_block = (ref_block.astype(float) + residual).clip(0, 255)
reconstructed_blocks.append(reconstructed_block.astype(np.uint8))
k += 1
return blocks_to_frame(reconstructed_blocks, h, w)
পর্ব ৪: সবকিছু একসাথে করা (`main.py`)
এই স্ক্রিপ্টটি পুরো প্রক্রিয়াটি পরিচালনা করে: একটি ভিডিও পড়া, এটিকে ফ্রেম বাই ফ্রেম এনকোড করা এবং তারপর একটি চূড়ান্ত আউটপুট তৈরি করতে এটিকে ডিকোড করা।
# main.py
import cv2
import pickle # For saving/loading our compressed data structure
from encoder import encode_iframe, encode_pframe
from decoder import decode_iframe, decode_pframe
def main(input_path, output_path, compressed_file_path):
cap = cv2.VideoCapture(input_path)
frames = []
while True:
ret, frame = cap.read()
if not ret:
break
# We'll work with the grayscale (luma) channel for simplicity
frames.append(cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY))
cap.release()
# --- ENCODING --- #
print("Encoding...")
compressed_data = []
reference_frame = None
gop_size = 12 # I-frame every 12 frames
for i, frame in enumerate(frames):
if i % gop_size == 0:
# Encode as I-frame
encoded_frame = encode_iframe(frame, qp=2.5)
compressed_data.append(encoded_frame)
print(f"Encoded frame {i} as I-frame")
else:
# Encode as P-frame
encoded_frame = encode_pframe(frame, reference_frame, qp=2.5)
compressed_data.append(encoded_frame)
print(f"Encoded frame {i} as P-frame")
# The reference for the next P-frame needs to be the *reconstructed* last frame
if encoded_frame['type'] == 'I':
reference_frame = decode_iframe(encoded_frame)
else:
reference_frame = decode_pframe(encoded_frame, reference_frame)
with open(compressed_file_path, 'wb') as f:
pickle.dump(compressed_data, f)
print(f"Compressed data saved to {compressed_file_path}")
# --- DECODING --- #
print("\nDecoding...")
with open(compressed_file_path, 'rb') as f:
loaded_compressed_data = pickle.load(f)
decoded_frames = []
reference_frame = None
for i, encoded_frame in enumerate(loaded_compressed_data):
if encoded_frame['type'] == 'I':
decoded_frame = decode_iframe(encoded_frame)
print(f"Decoded frame {i} (I-frame)")
else:
decoded_frame = decode_pframe(encoded_frame, reference_frame)
print(f"Decoded frame {i} (P-frame)")
decoded_frames.append(decoded_frame)
reference_frame = decoded_frame
# --- WRITING OUTPUT VIDEO --- #
h, w = decoded_frames[0].shape
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_path, fourcc, 30.0, (w, h), isColor=False)
for frame in decoded_frames:
out.write(frame)
out.release()
print(f"Decoded video saved to {output_path}")
if __name__ == '__main__':
main('input.mp4', 'output.mp4', 'compressed.bin')
ফলাফল বিশ্লেষণ এবং আরও অন্বেষণ
একটি `input.mp4` ফাইল দিয়ে `main.py` স্ক্রিপ্টটি চালানোর পরে, আপনি দুটি ফাইল পাবেন: `compressed.bin`, যা আমাদের কাস্টম সংকুচিত ভিডিও ডেটা ধারণ করে, এবং `output.mp4`, যা পুনর্গঠিত ভিডিও। কমপ্রেশন অনুপাত দেখতে `input.mp4` এর আকারের সাথে `compressed.bin`-এর আকারের তুলনা করুন। গুণমান দেখতে `output.mp4` চাক্ষুষভাবে পরিদর্শন করুন। আপনি সম্ভবত ব্লকি আর্টিফ্যাক্ট দেখতে পাবেন, বিশেষত একটি উচ্চতর `qp` মানের সাথে, যা কোয়ান্টাইজেশনের একটি ক্লাসিক লক্ষণ।
গুণমান পরিমাপ: পিক সিগন্যাল-টু-নয়েজ রেশিও (PSNR)
পুনর্গঠনের গুণমান পরিমাপ করার জন্য একটি সাধারণ অবজেক্টিভ মেট্রিক হলো PSNR। এটি মূল ফ্রেমের সাথে ডিকোড করা ফ্রেমের তুলনা করে। একটি উচ্চতর PSNR সাধারণত ಉತ್ತಮ গুণমান নির্দেশ করে।
import numpy as np
import math
def calculate_psnr(original, compressed):
mse = np.mean((original - compressed) ** 2)
if mse == 0:
return float('inf')
max_pixel = 255.0
psnr = 20 * math.log10(max_pixel / math.sqrt(mse))
return psnr
সীমাবদ্ধতা এবং পরবর্তী পদক্ষেপ
আমাদের সহজ কোডেকটি একটি দুর্দান্ত শুরু, তবে এটি নিখুঁত থেকে অনেক দূরে। এখানে কিছু সীমাবদ্ধতা এবং সম্ভাব্য উন্নতি রয়েছে যা বাস্তব-বিশ্বের কোডেকগুলির বিবর্তনকে প্রতিফলিত করে:
- মোশন এস্টিমেশন: আমাদের এক্সহস্টিভ সার্চ ধীর এবং প্রাথমিক। আসল কোডেকগুলি মোশন ভেক্টরগুলি অনেক দ্রুত খুঁজে বের করার জন্য অত্যাধুনিক, হায়ারারকিক্যাল সার্চ অ্যালগরিদম ব্যবহার করে।
- B-ফ্রেম: আমরা কেবল P-ফ্রেম প্রয়োগ করেছি। B-ফ্রেম যুক্ত করলে জটিলতা এবং লেটেন্সি বাড়ার বিনিময়ে কমপ্রেশন দক্ষতা উল্লেখযোগ্যভাবে উন্নত হবে।
- এনট্রপি কোডিং: আমরা একটি সঠিক এনট্রপি কোডিং পর্যায় প্রয়োগ করিনি। আমরা কেবল পাইথন ডেটা স্ট্রাকচারগুলিকে পিকেল করেছি। কোয়ান্টাইজড শূন্যগুলির জন্য একটি রান-লেংথ এনকোডার এবং তারপরে একটি হাফম্যান বা অ্যারিথমেটিক কোডার যুক্ত করলে ফাইলের আকার আরও কমবে।
- ডিব্লকিং ফিল্টার: আমাদের ৮x৮ ব্লকগুলির মধ্যেকার ধারালো প্রান্তগুলি দৃশ্যমান আর্টিফ্যাক্ট তৈরি করে। আধুনিক কোডেকগুলি এই প্রান্তগুলিকে মসৃণ করতে এবং চাক্ষুষ গুণমান উন্নত করতে পুনর্গঠনের পরে একটি ডিব্লকিং ফিল্টার প্রয়োগ করে।
- পরিবর্তনশীল ব্লক সাইজ: আধুনিক কোডেকগুলি কেবল নির্দিষ্ট ১৬x১৬ ম্যাক্রোব্লক ব্যবহার করে না। তারা কন্টেন্টের সাথে আরও ভালভাবে মেলানোর জন্য ফ্রেমটিকে বিভিন্ন আকারের এবং আকৃতির ব্লকে অভিযোজিতভাবে বিভক্ত করতে পারে (যেমন, সমতল অঞ্চলের জন্য বড় ব্লক এবং বিস্তারিত অঞ্চলের জন্য ছোট ব্লক ব্যবহার করে)।
উপসংহার
একটি ভিডিও কোডেক তৈরি করা, এমনকি একটি সরলীকৃত সংস্করণ হলেও, এটি একটি গভীরভাবে ফলপ্রসূ অনুশীলন। এটি সেই প্রযুক্তিকে রহস্যমুক্ত করে যা আমাদের ডিজিটাল জীবনের একটি উল্লেখযোগ্য অংশকে শক্তি জোগায়। আমরা স্থানিক এবং সময়গত পুনরাবৃত্তির মূল ধারণাগুলির মধ্য দিয়ে যাত্রা করেছি, এনকোডিং পাইপলাইনের অপরিহার্য পর্যায়গুলি—প্রেডিকশন, ট্রান্সফরমেশন এবং কোয়ান্টাইজেশন—পর্যালোচনা করেছি এবং এই ধারণাগুলি পাইথনে প্রয়োগ করেছি।
এখানে দেওয়া কোডটি একটি সূচনা বিন্দু। আমি আপনাকে এটি নিয়ে পরীক্ষা করার জন্য উৎসাহিত করছি। ব্লক সাইজ, কোয়ান্টাইজেশন প্যারামিটার (`qp`), বা GOP দৈর্ঘ্য পরিবর্তন করে দেখুন। একটি সহজ রান-লেংথ এনকোডিং স্কিম প্রয়োগ করার চেষ্টা করুন বা এমনকি B-ফ্রেম যুক্ত করার চ্যালেঞ্জ গ্রহণ করুন। জিনিসপত্র তৈরি এবং ভাঙার মাধ্যমে, আপনি সেই নির্বিঘ্ন ভিডিও অভিজ্ঞতার পেছনের চাতুর্যের জন্য একটি গভীর উপলব্ধি অর্জন করবেন যা আমরা প্রায়শই সাধারণ বলে ধরে নিই। ভিডিও কমপ্রেশনের জগৎ বিশাল এবং ক্রমাগত বিকশিত হচ্ছে, যা শেখার এবং উদ্ভাবনের জন্য অফুরন্ত সুযোগ প্রদান করে।