পাঠ ৪৫ · ৫৭-এর মধ্যে · মডিউল ১০
Home / Courses / Digital Signal Processing / স্পেকট্রাল এস্টিমেশন

শর্ট-টাইম ফুরিয়ার ট্রান্সফর্ম (STFT) ও স্পেকট্রোগ্রাম

The short-time Fourier transform (STFT) & spectrograms
১০ মিনিট পড়া মধ্যম-উচ্চ · Intermediate-Advanced Python কোডসহ সম্পূর্ণ বাংলায়

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

  • কেন সাধারণ DFT সময়ে পরিবর্তনশীল সিগন্যালের জন্য অপর্যাপ্ত
  • STFT-এর সংজ্ঞা — উইন্ডো + স্লাইডিং-ফ্রেম DFT-এর সমন্বয়
  • Python-এ হাতে-লেখা STFT দিয়ে একটি টেক্সট-ভিত্তিক স্পেকট্রোগ্রাম তৈরি করা
  • সময়-রেজোলিউশন বনাম ফ্রিকোয়েন্সি-রেজোলিউশনের মধ্যকার মৌলিক ট্রেড-অফ

১ · কেন সাধারণ DFT যথেষ্ট নয়

M২২-এর DFT একটি সম্পূর্ণ সিগন্যালকে একটি একক ফ্রিকোয়েন্সি-স্পেকট্রামে রূপান্তর করে — এটি বলে দেয় সিগন্যালে কোন কোন ফ্রিকোয়েন্সি আছে, কিন্তু কখন তা ঘটেছে সে তথ্য হারিয়ে যায় (গাণিতিকভাবে, DFT সূত্রের সামেশনে সময়-তথ্য সম্পূর্ণ "মিশে" যায়)। বাস্তব সিগন্যাল — কথা বলা, গান, একটি যন্ত্র চালু-বন্ধ হওয়া — প্রায় সবসময় সময়ের সাথে সাথে ফ্রিকোয়েন্সি-বিষয়বস্তু বদলায়।

STFTShort-Time Fourier Transformসিগন্যালকে ছোট ছোট সময়-ফ্রেমে ভাগ করে প্রতিটি ফ্রেমে আলাদাভাবে উইন্ডো-করা DFT প্রয়োগকারী পদ্ধতি, যার ফলাফল সময় ও ফ্রিকোয়েন্সি উভয়ের ফাংশন। এই সমস্যার সমাধান করে সিগন্যালকে ছোট ছোট ফ্রেমে ভাগ করে:

$$X[m, k] = \sum_{n=0}^{L-1} w[n]\, x[n + mH]\, e^{-j2\pi kn/L}$$

এখানে $L$ ফ্রেমের দৈর্ঘ্য, $H$ হপ-সাইজ (একটি ফ্রেম থেকে পরেরটিতে কত স্যাম্পল এগোনো হয়), $w[n]$ M২৪-এর মতো একটি উইন্ডো ফাংশন (Hamming/Hann), আর $m$ ফ্রেম-ইনডেক্স (সময়)। প্রতিটি $m$-এর জন্য একটি সম্পূর্ণ DFT বের হয় — ফলাফল একটি ২D গ্রিড: সময় ($m$) বনাম ফ্রিকোয়েন্সি ($k$)। এই গ্রিডের ম্যাগনিটিউড ছবি আকারে দেখানো হলে তাকে বলে স্পেকট্রোগ্রাম।

উইন্ডো কেন দরকার
প্রতিটি ফ্রেমকে সরাসরি কেটে নিলে ফ্রেমের প্রান্তে হঠাৎ অসংলগ্নতা তৈরি হয়ে M২৪-এর স্পেকট্রাল লিকেজ বাড়ে — Hamming/Hann উইন্ডো প্রান্তের দিকে সিগন্যাল মসৃণভাবে শূন্যের কাছে নামিয়ে আনে।
হপ-সাইজ ও ওভারল্যাপ
হপ-সাইজ ফ্রেম-দৈর্ঘ্যের চেয়ে ছোট হলে ফ্রেমগুলো ওভারল্যাপ করে, যা সময়-অক্ষে মসৃণতর ট্রানজিশন দেয় — নিচের ডেমোতে সরলতার জন্য হপ = ফ্রেম-দৈর্ঘ্য (নন-ওভারল্যাপিং) ব্যবহার করা হয়েছে।
Welch's method-এর সাথে সম্পর্ক
L৪৪-এর Welch's method ঠিক একই "সিগন্যাল ভাগ করো, প্রতিটি অংশে উইন্ডো + DFT নাও" কাঠামো ব্যবহার করে — পার্থক্য শুধু Welch গড় নেয় একটি স্থির স্পেকট্রামের জন্য, STFT প্রতিটি ফ্রেম আলাদা রাখে।

২ · একটি সত্যিকারের ডেমো — ফ্রিকোয়েন্সি-পরিবর্তন শনাক্তকরণ

নিচের কোডে একটি ৬৪-স্যাম্পলের সিগন্যাল তৈরি করা হয়েছে ($f_s=64$ Hz) — প্রথম ৩২ স্যাম্পল একটি $4$ Hz সাইন-তরঙ্গ, শেষ ৩২ স্যাম্পল একটি $12$ Hz সাইন-তরঙ্গ (দুটো অংশ সরাসরি জোড়া দেওয়া)। ফ্রেম-দৈর্ঘ্য $L=16$, হপ $H=16$ (নন-ওভারল্যাপিং, তাই মোট ৪টি ফ্রেম), প্রতিটি ফ্রেমে Hamming উইন্ডো প্রয়োগ করে M৫-এর হাতে-লেখা DFT দিয়ে ম্যাগনিটিউড স্পেকট্রাম বের করা হয়েছে — কোনো ইমেজ লাইব্রেরি ছাড়াই একটি টেক্সট-টেবিল হিসেবে "স্পেকট্রোগ্রাম" প্রিন্ট করা হয়েছে।

Python
import math, cmath

def dft(x):
    N = len(x)
    X = []
    for k in range(N):
        s = 0 + 0j
        for n in range(N):
            s += x[n] * cmath.exp(-2j * math.pi * k * n / N)
        X.append(s)
    return X

def hamming(N):
    return [0.54 - 0.46 * math.cos(2 * math.pi * n / (N - 1)) for n in range(N)]

fs = 64.0
f_low = 4.0
f_high = 12.0
half = 32
low_seg = [math.sin(2 * math.pi * f_low * n / fs) for n in range(half)]
high_seg = [math.sin(2 * math.pi * f_high * n / fs) for n in range(half)]
x = low_seg + high_seg          # প্রথম অর্ধেক নিম্ন-ফ্রিকোয়েন্সি, দ্বিতীয় অর্ধেক উচ্চ-ফ্রিকোয়েন্সি
N_total = len(x)

frame_size = 16
hop = 16
win = hamming(frame_size)

frame_starts = []
start = 0
while start + frame_size <= N_total:
    frame_starts.append(start)
    start += hop

n_bins = frame_size // 2 + 1     # 0 থেকে Nyquist পর্যন্ত বিন
table = []
for fstart in frame_starts:
    seg = x[fstart:fstart + frame_size]
    wseg = [seg[i] * win[i] for i in range(frame_size)]
    X = dft(wseg)
    table.append([abs(X[k]) for k in range(n_bins)])

header = "frame | " + " | ".join(f"{k*fs/frame_size:>5.0f}Hz" for k in range(n_bins))
print(header)
for i, mags in enumerate(table):
    row = f"{i:>5} | " + " | ".join(f"{m:>6.2f}" for m in mags)
    print(row)

print()
for i, mags in enumerate(table):
    peak_bin = max(range(n_bins), key=lambda k: mags[k])
    peak_freq = peak_bin * fs / frame_size
    s0, s1 = frame_starts[i], frame_starts[i] + frame_size - 1
    print(f"frame {i} (samples {s0}-{s1}): পিক bin {peak_bin} (={peak_freq} Hz), ম্যাগনিটিউড {mags[peak_bin]:.2f}")

    
রান করলে টেক্সট-স্পেকট্রোগ্রামে স্পষ্ট দেখা যায়: ফ্রেম ০ ও ১-এ (samples ০-৩১, মূল সিগন্যালের নিম্ন-ফ্রিকোয়েন্সি অংশ) পিক ৪ Hz বিনে, ম্যাগনিটিউড ৪.০১ — ফ্রেম ২ ও ৩-এ (samples ৩২-৬৩, উচ্চ-ফ্রিকোয়েন্সি অংশ) পিক ১২ Hz বিনে সরে যায়, ম্যাগনিটিউড ৪.০৯। প্রতিটি ফ্রেমে অন্য বিনগুলোর মান তুলনামূলক অনেক ছোট (০.০০ থেকে ১.৯২-এর মধ্যে) — অর্থাৎ প্রতিটি সময়-মুহূর্তে সঠিক প্রভাবশালী ফ্রিকোয়েন্সি স্পষ্টভাবে ধরা পড়েছে, এবং সিগন্যালের মাঝামাঝি ঠিক যেখানে ফ্রিকোয়েন্সি বদলেছে (sample ৩২), STFT-এর ফ্রেম-বিভাজনও ঠিক সেখানেই পড়ে যাওয়ায় (frame_size=১৬, hop=১৬ হওয়ায়) পরিবর্তনটি একদম পরিষ্কারভাবে ধরা পড়েছে — এটাই একক DFT দিয়ে অসম্ভব, কারণ সেখানে দুটো ফ্রিকোয়েন্সিই একসাথে মিশে দেখাত, "কখন কোনটা" তা বলা যেত না।

৩ · সময়-ফ্রিকোয়েন্সি ট্রেড-অফ

STFT-এর একটি মৌলিক সীমাবদ্ধতা আছে — ফ্রেম-দৈর্ঘ্য $L$ একসাথে সময়-রেজোলিউশন ও ফ্রিকোয়েন্সি-রেজোলিউশন উভয়ই নিয়ন্ত্রণ করে, কিন্তু বিপরীত দিকে:

ছোট ফ্রেম
ভালো সময়-রেজোলিউশন (ফ্রিকোয়েন্সি-পরিবর্তন দ্রুত ধরা পড়ে) কিন্তু M২৫-এর নিয়ম অনুযায়ী কম স্যাম্পল মানে কম ফ্রিকোয়েন্সি-রেজোলিউশন — বিন-স্পেসিং $f_s/L$ বড় হয়ে যায়।
বড় ফ্রেম
ভালো ফ্রিকোয়েন্সি-রেজোলিউশন (কাছাকাছি ফ্রিকোয়েন্সি আলাদা করা যায়) কিন্তু একটি ফ্রেমের ভেতরে ফ্রিকোয়েন্সি বদলালে সেই পরিবর্তন ফ্রেমের গড়ে "ঝাপসা" হয়ে যায়।

উপরের ডেমোতে $L=16$ ব্যবহার করায় বিন-স্পেসিং $64/16=4$ Hz — যথেষ্ট বড় ফাঁক, কিন্তু যেহেতু ৪ Hz ও ১২ Hz উভয়ই ঠিক বিনের উপর পড়ে (bin ১ ও bin ৩), তাই এই নির্দিষ্ট উদাহরণে সমস্যা হয়নি। বাস্তব অ্যাপ্লিকেশনে (যেমন অডিও স্পেকট্রোগ্রাম, M৫২-এ বিস্তারিত) এই ট্রেড-অফ বিবেচনা করে $L$ বেছে নেওয়া হয়।

মূল কথা · Key takeaway

STFT সিগন্যালকে ছোট ছোট ফ্রেমে ভাগ করে প্রতিটিতে M২৪-এর উইন্ডো ও M৫-M৬-এর DFT প্রয়োগ করে সময় ও ফ্রিকোয়েন্সি উভয়ের একটি যৌথ চিত্র দেয় — উপরের ডেমো দেখাল এটি সত্যিকারের ফ্রিকোয়েন্সি-পরিবর্তন (৪ Hz থেকে ১২ Hz) স্পষ্টভাবে ধরতে পারে। তবে ফ্রেম-দৈর্ঘ্যের পছন্দ সময়-রেজোলিউশন ও ফ্রিকোয়েন্সি-রেজোলিউশনের মধ্যে একটি অনিবার্য আপস। পরের পাঠে (L৪৬) আমরা একটি ভিন্ন ট্রান্সফর্ম — DCT — দেখব, যা কমপ্রেশনের জন্য বিশেষভাবে উপযোগী কারণ এটি এনার্জি খুব কম কয়েকটি কোয়েফিসিয়েন্টে কেন্দ্রীভূত করে।

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

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

প্র ০১ যদি পুরো ৬৪-স্যাম্পল সিগন্যালের উপর একটিমাত্র সাধারণ ৬৪-বিন্দুর DFT নেওয়া হতো (STFT ছাড়া), তাহলে স্পেকট্রামে কী দেখা যেত?

স্পেকট্রামে ৪ Hz আর ১২ Hz — দুটো বিনেই পিক দেখা যেত (প্রতিটি সিগন্যালের অর্ধেক অংশ, তাই মোট এনার্জির প্রায় সমান ভাগ), কিন্তু কোনটা "আগে" আর কোনটা "পরে" ঘটেছে তার কোনো তথ্য থাকত না — DFT শুধু বলত "এই দুটো ফ্রিকোয়েন্সি সিগন্যালে আছে", সময়ের ক্রম সম্পূর্ণ হারিয়ে যেত। উপরের STFT ডেমো ঠিক এই সময়-তথ্যটাই পুনরুদ্ধার করে দেখায়।

প্র ০২ ফ্রেম ০ আর ফ্রেম ১-এর ম্যাগনিটিউড টেবিল হুবহু একই (৪.০১, ১.৯২, ...)। এটি কি প্রত্যাশিত?

হ্যাঁ, সম্পূর্ণ প্রত্যাশিত। যেহেতু পুরো নিম্ন-ফ্রিকোয়েন্সি অংশ (samples ০-৩১) একটি বিশুদ্ধ, ধ্রুবক অ্যামপ্লিটিউডের ৪ Hz সাইন-তরঙ্গ, এবং ফ্রেম ০ (samples ০-১৫) ও ফ্রেম ১ (samples ১৬-৩১) উভয়েই এই একই সিগন্যালের সমান-দৈর্ঘ্যের অংশ, তাদের উইন্ডো-করা DFT ফলাফল হুবহু এক হওয়ারই কথা (সিগন্যাল প্রতি ১৬ স্যাম্পলে পর্যায়বৃত্ত, কারণ $f_{low}=4$ Hz আর $f_s/f_{low}=16$)।

প্র ০৩ যদি ফ্রেম-দৈর্ঘ্য $L=64$ (পুরো সিগন্যাল একটি ফ্রেমে) করা হতো, STFT কি তখনও ফ্রিকোয়েন্সি-পরিবর্তন দেখাতে পারত?

না। $L=64$ মানে মাত্র একটি ফ্রেম থাকবে — এটি কার্যত সাধারণ DFT-তে পরিণত হয়ে যাবে (প্র ০১-এর উত্তরের মতো), সময়-রেজোলিউশন সম্পূর্ণ হারিয়ে যাবে। এটাই ৩ নং অংশের ট্রেড-অফের একটি চরম উদাহরণ — সবচেয়ে বড় সম্ভাব্য ফ্রেম সবচেয়ে ভালো ফ্রিকোয়েন্সি-রেজোলিউশন দেয় কিন্তু শূন্য সময়-রেজোলিউশন।

অনুশীলন

  1. চিন্তা করুন: উপরের কোডে frame_size = 16-কে frame_size = 8-এ বদলালে (hop-ও ৮ করে), মোট কতগুলো ফ্রেম তৈরি হবে বলে আপনার ধারণা? আর বিন-স্পেসিং কত হবে ($f_s/L$ সূত্র ব্যবহার করে)?

    মোট স্যাম্পল ৬৪, frame_size ও hop উভয়ই ৮ হলে $64/8=8$টি ফ্রেম তৈরি হবে (আগের ৪টির দ্বিগুণ, ভালো সময়-রেজোলিউশন)। বিন-স্পেসিং হবে $f_s/L = 64/8 = 8$ Hz — আগের ৪ Hz-এর চেয়ে বেশি বিস্তৃত (কম ফ্রিকোয়েন্সি-রেজোলিউশন), ঠিক ৩ নং অংশের ট্রেড-অফ অনুযায়ী।

  2. পরীক্ষা করুন: কোড সেলে frame_size ও hop উভয়ই ৮-এ পরিবর্তন করে Run চেপে দেখুন — ফ্রিকোয়েন্সি-পরিবর্তন এখনও স্পষ্ট ধরা পড়ে কি না, আর ফ্রেম-সংখ্যা আপনার হিসেবের সাথে মেলে কি না।

    রান করলে ৮টি ফ্রেম দেখাবে (frame ০-৩ নিম্ন-ফ্রিকোয়েন্সি অংশে, frame ৪-৭ উচ্চ-ফ্রিকোয়েন্সি অংশে), আর পরিবর্তনটি frame ৩ ও frame ৪-এর মাঝে এখনও স্পষ্ট দেখাবে — বরং আরও নিখুঁতভাবে, কারণ ছোট ফ্রেম সময়ের সাথে দ্রুত পরিবর্তন আরও সূক্ষ্মভাবে ধরতে পারে।

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

আগের পাঠ
পাওয়ার স্পেকট্রাল ডেনসিটি এস্টিমেশন