পাঠ ২২ · ৫৭-এর মধ্যে · মডিউল ৫
Home / Courses / Digital Signal Processing / ফ্রিকোয়েন্সি-ডোমেইন অ্যানালাইসিস

ডিসক্রিট ফুরিয়ার ট্রান্সফর্ম (DFT)

The discrete Fourier transform (DFT)
১১ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • DFT-এর সংজ্ঞা, DTFT থেকে এর উৎপত্তি, এবং $O(N^2)$ সরাসরি সামেশন সূত্র
  • Python-এ সম্পূর্ণ হাতে-লেখা একটি DFT ফাংশন বাস্তবায়ন করা (স্ট্যান্ডার্ড লাইব্রেরি ছাড়া)
  • DFT বিন ইনডেক্স $k$-কে প্রকৃত ফ্রিকোয়েন্সি (Hz)-এ রূপান্তর করা ($f_k = k \cdot f_s / N$)
  • একটি সিগন্যালের জানা ফ্রিকোয়েন্সি DFT-এর পিক ম্যাগনিটিউড বিন থেকে সত্যিকারের গণনা দিয়ে পুনরুদ্ধার করা

১ · DTFT থেকে DFT — কেন এই পরিবর্তন দরকার

L২০-এ দেখেছি DTFT $X(e^{j\omega})$ একটি কন্টিনিউয়াস ফাংশন — $\omega$ যেকোনো বাস্তব মান নিতে পারে। কম্পিউটারে এটি সরাসরি সংরক্ষণ করা অসম্ভব (অসীম সংখ্যক মান)। সমাধান: $\omega$-কে ঠিক $N$টি সমান-ব্যবধানের মানে স্যাম্পল করা, $\omega_k = \frac{2\pi k}{N}$, যেখানে $k = 0, 1, \ldots, N-1$। এই স্যাম্পল-করা DTFT-ই DFTDiscrete Fourier Transformএকটি সসীম-দৈর্ঘ্যের সিগন্যালকে ঠিক N-টি সমান-ব্যবধানের ফ্রিকোয়েন্সি-বিনে রূপান্তরকারী সসীম সামেশন সূত্র — সম্পূর্ণভাবে কম্পিউটারে গণনাযোগ্য।:

$$X[k] = \sum_{n=0}^{N-1} x[n]\, e^{-j\frac{2\pi}{N}kn}, \qquad k = 0, 1, \ldots, N-1$$

প্রতিটি $k$-কে একটি ফ্রিকোয়েন্সি বিন বলা হয়, যা প্রকৃত ফ্রিকোয়েন্সি $f_k = \dfrac{k \cdot f_s}{N}$ Hz-এর সাথে সংশ্লিষ্ট (যেখানে $f_s$ হলো স্যাম্পল রেট, M২-তে সংজ্ঞায়িত)। বিন-থেকে-বিনের ব্যবধান, অর্থাৎ ফ্রিকোয়েন্সি রেজোলিউশন, হলো $f_s/N$ Hz — এটি L২৫-এ গুরুত্বপূর্ণ হয়ে উঠবে।

$O(N^2)$ জটিলতা
$N$টি বিনের প্রতিটির জন্য $N$টি পদ যোগ করতে হয় — মোট $N \times N = N^2$ কমপ্লেক্স গুণন। ছোট $N$-এ ঠিক আছে, বড় $N$-এ ধীর — M৬-এ FFT এই সমস্যার সমাধান।
সসীম, সসীম
ইনপুট $N$টি সংখ্যা, আউটপুটও $N$টি (কমপ্লেক্স) সংখ্যা — কোনো অসীম সামেশন নেই, তাই সরাসরি কোডে বাস্তবায়নযোগ্য।
বিন থেকে Hz
$f_k = k \cdot f_s/N$ — এই রূপান্তর সূত্রটি DFT ব্যবহারের সবচেয়ে গুরুত্বপূর্ণ ব্যবহারিক পদক্ষেপ, ভুল করলে ফ্রিকোয়েন্সি রিডিং ভুল হয়ে যায়।

২ · হাতে-লেখা DFT বাস্তবায়ন

নিচে সরাসরি সূত্র অনুযায়ী একটি DFT ফাংশন লেখা হয়েছে — শুধু cmath.exp ও নেস্টেড লুপ, কোনো NumPy/SciPy নেই। এটি এই কোর্সের বাকি অংশে বারবার পুনরায় ব্যবহৃত হবে, তাই এখানে সঠিকভাবে যাচাই করা গুরুত্বপূর্ণ।

Python
import cmath, math

def dft(x):
    """সরাসরি O(N^2) সামেশন সূত্র দিয়ে DFT -- এই কোর্সে বারবার পুনর্ব্যবহৃত হবে।"""
    N = len(x)
    X = []
    for k in range(N):
        s = sum(x[n] * cmath.exp(-2j * math.pi * k * n / N) for n in range(N))
        X.append(s)
    return X

fs = 64.0   # স্যাম্পল রেট (Hz)
N = 32      # স্যাম্পল সংখ্যা
f1, f2 = 8.0, 20.0   # প্রকৃত (জানা) ফ্রিকোয়েন্সি, Hz
a1, a2 = 1.0, 0.5    # অ্যামপ্লিটিউড

x = [a1 * math.sin(2 * math.pi * f1 * n / fs) + a2 * math.sin(2 * math.pi * f2 * n / fs)
     for n in range(N)]

X = dft(x)
mags = [abs(v) for v in X]

print(f"fs={fs} Hz, N={N}, বিন রেজোলিউশন fs/N = {fs/N} Hz")
print(f"{'k':>3} | {'freq (Hz)':>9} | {'|X[k]|':>8}")
for k in range(N // 2 + 1):
    print(f"{k:3d} | {k*fs/N:9.2f} | {mags[k]:8.3f}")

    
আউটপুটে দেখা যায় $k=0$ থেকে $k=16$ ($N/2$) পর্যন্ত প্রায় সব বিনের ম্যাগনিটিউড শূন্য, শুধু $k=4$-এ $|X[4]| = 16.000$ আর $k=10$-এ $|X[10]| = 8.000$ — বাকি সব বিন সংখ্যাগতভাবে $0$। যেহেতু বিন রেজোলিউশন $f_s/N = 2$ Hz, তাই $k=4 \Rightarrow f = 4 \times 2 = 8$ Hz আর $k=10 \Rightarrow f = 10 \times 2 = 20$ Hz — ঠিক আমাদের সেট-করা প্রকৃত ফ্রিকোয়েন্সি $f_1=8$ Hz আর $f_2=20$ Hz-এর সাথে হুবহু মিলে যায়! ম্যাগনিটিউডের অনুপাতও ($16$ বনাম $8$, অর্থাৎ ঠিক $2:1$) আমাদের সেট করা অ্যামপ্লিটিউড অনুপাত $a_1{:}a_2 = 1.0{:}0.5 = 2{:}1$-এর সাথে সামঞ্জস্যপূর্ণ (একটি $N$-পয়েন্ট DFT-তে একটি বিশুদ্ধ সাইন-তরঙ্গের পিক ম্যাগনিটিউড $N \cdot a/2$ হয়, যা এখানে $32 \times 1.0/2 = 16$ আর $32 \times 0.5/2 = 8$ — সংখ্যাগতভাবেও মিলে যায়)।

৩ · পিক-বিন খুঁজে ফ্রিকোয়েন্সি পুনরুদ্ধার

বাস্তব ব্যবহারে আমরা কোন বিনে পিক আছে তা "চোখে দেখে" নয়, বরং সর্বোচ্চ ম্যাগনিটিউডের বিন প্রোগ্রামগতভাবে খুঁজে বের করি। নিচের কোডে (উপরের x, X, mags ব্যবহার করে) সেই অনুসন্ধান দেখানো হলো।

Python
# উপরের কোষের x, X, mags, fs, N ব্যবহার করে --
# k=0 (DC) বাদ দিয়ে, k=1..N/2 রেঞ্জে সর্বোচ্চ দুটো ম্যাগনিটিউডের বিন খোঁজা
sorted_bins = sorted(range(1, N // 2 + 1), key=lambda k: -mags[k])
top2 = sorted_bins[:2]

print("সর্বোচ্চ ম্যাগনিটিউডের বিন (k=1..N/2):", top2)
for k in top2:
    f_recovered = k * fs / N
    print(f"  k={k} -> পুনরুদ্ধারকৃত ফ্রিকোয়েন্সি = {f_recovered} Hz, |X[k]| = {mags[k]:.4f}")

print()
print(f"প্রকৃত ফ্রিকোয়েন্সি ছিল: {f1} Hz ও {f2} Hz")

    
আউটপুট: সর্বোচ্চ দুটো বিন [4, 10], যা থেকে পুনরুদ্ধারকৃত ফ্রিকোয়েন্সি $8.0$ Hz ও $20.0$ Hz — প্রকৃত ফ্রিকোয়েন্সি $8.0$ Hz ও $20.0$ Hz-এর সাথে হুবহু নির্ভুলভাবে মিলে যায় (কারণ এই উদাহরণে উভয় ফ্রিকোয়েন্সি ঠিক পূর্ণসংখ্যা বিনে পড়েছে)। এটাই DFT-এর সবচেয়ে ব্যবহারিক প্রয়োগ — একটি অজানা সিগন্যালে কোন কোন ফ্রিকোয়েন্সি লুকিয়ে আছে তা সত্যিকারের গণনা দিয়ে বের করা।
মূল কথা · Key takeaway

এই পাঠের dft() ফাংশন আর "বিন-থেকে-Hz" রূপান্তর সূত্র ($f_k = k \cdot f_s/N$) হলো এই কোর্সের সবচেয়ে গুরুত্বপূর্ণ পুনর্ব্যবহারযোগ্য বিল্ডিং ব্লক — L২৩-এ এর বৈশিষ্ট্য যাচাই, L২৪-এ উইন্ডোয়িং, L২৫-এ জিরো-প্যাডিং, M৬-এ FFT-এর সাথে ক্রস-চেক, এবং M৭-M১০-এ ফিল্টার ও স্পেকট্রাল-এস্টিমেশন যাচাইয়ে বারবার এই একই প্যাটার্ন ফিরে আসবে। তবে মনে রাখবেন — এখানে ফ্রিকোয়েন্সিগুলো ঠিক পূর্ণসংখ্যা বিনে পড়েছিল বলেই ফলাফল এত পরিষ্কার হয়েছে; L২৪-L২৫-এ দেখা যাবে বাস্তব সিগন্যাল সবসময় এত সৌভাগ্যবান হয় না।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ উপরের ডেমোতে $N=32$, $f_s=64$ Hz দিয়ে বিন রেজোলিউশন $2$ Hz পাওয়া গেল। যদি $N=64$ করা হতো (একই $f_s$), রেজোলিউশন কী হতো, আর এটি কি ভালো নাকি খারাপ?

$N=64$-এ রেজোলিউশন $f_s/N = 64/64 = 1$ Hz হতো — অর্থাৎ আগের চেয়ে দ্বিগুণ ভালো (সূক্ষ্ম) রেজোলিউশন, কারণ বিন-থেকে-বিনের ব্যবধান কমে গেছে। কিন্তু বেশি $N$ মানে বেশি real স্যাম্পল দরকার (দীর্ঘ সময় ধরে সিগন্যাল রেকর্ড করা), এবং $O(N^2)$ গণনাও বেড়ে যায় — L২৫-এ দেখা যাবে শুধু জিরো-প্যাড করে (নকল স্যাম্পল যোগ করে) এই একই সুবিধা পাওয়া যায় না।

প্র ০২ যদি প্রকৃত ফ্রিকোয়েন্সি $f_1=8$ Hz-এর বদলে $f_1=9$ Hz হতো (একই $f_s=64$, $N=32$), তাহলে পিক কি একটি একক বিনে ঠিক পড়ত?

না। $9$ Hz-কে বিন ইনডেক্সে রূপান্তর করলে $k = f/(f_s/N) = 9/2 = 4.5$ — একটি ভগ্নাংশ, কোনো পূর্ণসংখ্যা বিন নয়। ফলে শক্তি $k=4$ ও $k=5$-এর মধ্যে "ছড়িয়ে" পড়বে, এবং আশেপাশের বিনগুলোতেও কিছুটা শক্তি "লিক" করবে — এটাই স্পেকট্রাল লিকেজ, যা L২৪-এ বিস্তারিত আলোচনা করা হবে।

প্র ০৩ উপরের কোডে sorted_bins খোঁজার সময় $k=0$ (DC বিন) বাদ দেওয়া হয়েছে কেন?

$k=0$ সবসময় সিগন্যালের গড় মান (DC কম্পোনেন্ট) প্রতিনিধিত্ব করে, দোদুল্যমান (oscillating) কোনো ফ্রিকোয়েন্সি নয়। আমাদের টেস্ট সিগন্যাল দুটো সাইন-তরঙ্গের যোগফল, যাদের গড় মান শূন্য, তাই এখানে $X[0]\approx 0$ হবে — কিন্তু সাধারণভাবে যদি সিগন্যালে কোনো ধ্রুবক অফসেট (DC bias) থাকে, তাহলে $X[0]$ বড় হতে পারে এবং প্রকৃত দোদুল্যমান ফ্রিকোয়েন্সি খোঁজার সময় এটিকে ভুলবশত "পিক" ধরে ফেলা এড়াতে সবসময় $k \geq 1$ থেকে খোঁজা ভালো অভ্যাস।

অনুশীলন

  1. চিন্তা করুন: যদি fs = 64.0, N = 32 অপরিবর্তিত রেখে f1 = 8.0-কে f1 = 16.0-এ বদলানো হয়, ফ্রিকোয়েন্সি বিন-ইনডেক্স $k$ কত হবে বলে আপনার ধারণা ($k = f \cdot N / f_s$ সূত্র ব্যবহার করে)?

    $k = f_1 \cdot N / f_s = 16 \times 32 / 64 = 8$। অর্থাৎ $k=8$ বিনে পিক পড়বে, যা $f = 8 \times 64/32 = 16$ Hz-এর সাথে মেলে — একটি পূর্ণসংখ্যা বিন, তাই এখানেও পরিষ্কার একক-বিন পিক পাওয়া উচিত।

  2. পরীক্ষা করুন: উপরের প্রথম কোড সেলে f1 = 8.0-কে f1 = 16.0-এ বদলে Run চাপুন এবং দ্বিতীয় কোড সেলটিও (একই x ব্যবহার করে, যেহেতু কোষগুলো একই সেশনে চলে) রান করে পিক-বিন খুঁজে যাচাই করুন আপনার হিসেব ঠিক ছিল কিনা।

    রান করলে প্রথম কোষে $k=8$-এ $|X[8]| = 16.000$ দেখাবে (আগের মতোই $16$, কারণ অ্যামপ্লিটিউড $a_1=1.0$ অপরিবর্তিত), আর $k=10$-এ $f_2=20$ Hz-এর পিক ($|X[10]|=8.000$) আগের মতোই থাকবে। দ্বিতীয় কোষে top2 হবে [8, 10], পুনরুদ্ধারকৃত ফ্রিকোয়েন্সি $16.0$ Hz ও $20.0$ Hz — হিসেবের সাথে হুবহু মিলে যায়।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

আগের পাঠ
LTI সিস্টেমের ফ্রিকোয়েন্সি রেসপন্স