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

ডাউনস্যাম্পলিং (ডেসিমেশন)

Downsampling (decimation)
১০ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

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

  • ডেসিমেশনের সংজ্ঞা, নতুন স্যাম্পল রেট, এবং কেন এটি লিস্ট স্লাইসিং-এর মতোই সহজ দেখতে হলেও বিপজ্জনক
  • কেন প্রতি M-তম স্যাম্পল রাখলে কার্যকর নাইকুইস্ট সীমা fs/(2M)-এ নেমে আসে
  • DFT ম্যাগনিচিউড দিয়ে সত্যিকারের অ্যালিয়াসিং আর্টিফ্যাক্ট প্রত্যক্ষভাবে দেখা এবং পরিমাপ করা
  • অ্যান্টি-অ্যালিয়াস ফিল্টার + ডেসিমেশনের সঠিক পাইপলাইন বাস্তবায়ন এবং তার প্রকৃত উন্নতি যাচাই করা

১ · ডেসিমেশন কী — কার্যকর স্যাম্পল রেট কমে যাওয়া

ডেসিমেশনDecimationএকটি ডিসক্রিট সিগন্যাল থেকে প্রতি M-তম স্যাম্পল রেখে বাকি সবগুলো বাদ দিয়ে স্যাম্পল রেট M গুণ কমানোর প্রক্রিয়া। হলো একটি সিগন্যালকে M ফ্যাক্টরে ডাউনস্যাম্পল করা — গাণিতিকভাবে:

$$y[n] = x[nM]$$

Python-এ এটি নিছক লিস্ট স্লাইসিং x[::M]। যদি মূল সিগন্যাল fs Hz-এ স্যাম্পল করা হয়ে থাকে, ডেসিমেটেড সিগন্যালের কার্যকর স্যাম্পল রেট হয় fs' = fs / M। এর মানে নতুন নাইকুইস্ট সীমাও M গুণ কমে যায়:

$$f_{\text{Nyquist,new}} = \frac{fs'}{2} = \frac{fs}{2M}$$

সমস্যাটা এখানেই — মূল সিগন্যালে এমন ফ্রিকোয়েন্সি উপাদান থাকতে পারে যা পুরনো নাইকুইস্ট সীমার নিচে (নিরাপদ ছিল), কিন্তু নতুন, ছোট নাইকুইস্ট সীমার উপরে (M5/M6-এ শেখা নাইকুইস্ট-শ্যানন থিওরেম অনুযায়ী)। এমন কোনো উপাদান থাকলে সেটি ডেসিমেশনের সময় অ্যালিয়াস করবে — L06-এ দেখা অ্যালিয়াসিং-এর একই ঘটনা, শুধু এখানে এটি "প্রসেসিং-এর ভেতরে" ঘটছে, রেকর্ডিং-এর সময় নয়।

লিস্ট স্লাইসিং
x[::M] — কোডে যতটা সহজ দেখতে, ততটাই সহজে অ্যালিয়াসিং তৈরি করতে পারে যদি সতর্ক না থাকা হয়।
নাইকুইস্ট সীমা M গুণ ছোট
fs/2 থেকে fs/(2M)-এ নেমে যায় — মূল সিগন্যালে "নিরাপদ" থাকা উপাদানও নতুন সীমার উপরে পড়ে যেতে পারে।
সমাধান — M7-এর FIR ফিল্টার
ডেসিমেশনের আগে একটি লো-পাস অ্যান্টি-অ্যালিয়াস ফিল্টার প্রয়োগ করে বিপজ্জনক উচ্চ-ফ্রিকোয়েন্সি উপাদান সরিয়ে ফেলা।

২ · অ্যান্টি-অ্যালিয়াস ফিল্টার ছাড়া ডেসিমেশন — সত্যিকারের অ্যালিয়াসিং

একটি টেস্ট সিগন্যাল তৈরি করা যাক — fs = 48 Hz-এ স্যাম্পল করা, একটি ধীর ৩ Hz আর একটি দ্রুত ৮ Hz (অ্যামপ্লিটিউড ০.৮) উপাদানের যোগফল। M = 4 দিয়ে নিছক x[::4] করলে নতুন রেট fs' = 12 Hz, নতুন নাইকুইস্ট সীমা ৬ Hz। ৩ Hz নিরাপদ, কিন্তু ৮ Hz নতুন সীমার উপরে — M5-এর হাতে-লেখা DFT দিয়ে দেখা যাক এর পরিণতি কী হয়।

Python
import math

def dft_magnitude(x, fs):
    """হাতে-লেখা DFT (M5) — ফ্রিকোয়েন্সি বিন k=0..N/2 পর্যন্ত ম্যাগনিচিউড ফেরত দেয়"""
    N = len(x)
    mags = []
    for k in range(N // 2 + 1):
        re = sum(x[n] * math.cos(2 * math.pi * k * n / N) for n in range(N))
        im = -sum(x[n] * math.sin(2 * math.pi * k * n / N) for n in range(N))
        mag = math.sqrt(re**2 + im**2)
        mags.append((k * fs / N, mag))
    return mags

fs = 48.0
N = 48
f_low, f_high = 3.0, 8.0     # দুটি উপাদান: ধীর ৩ Hz, দ্রুত ৮ Hz (অ্যামপ্লিটিউড ০.৮)

x = [math.sin(2*math.pi*f_low*n/fs) + 0.8*math.sin(2*math.pi*f_high*n/fs) for n in range(N)]

M = 4
fs_new = fs / M              # ১২ Hz -- নতুন নাইকুইস্ট সীমা ৬ Hz
x_naive = x[::M]             # নিছক প্রতি চতুর্থ স্যাম্পল রাখা -- কোনো ফিল্টার নেই!

print(f"মূল রেট fs = {fs} Hz, N = {N}  |  ডেসিমেটেড রেট fs' = {fs_new} Hz, N' = {len(x_naive)}")
print(f"নতুন নাইকুইস্ট সীমা: {fs_new/2} Hz  --  ৮ Hz উপাদানটি এই সীমার উপরে!")

# ৮ Hz উপাদানটি নতুন রেটে কোন ফ্রিকোয়েন্সিতে "ফোল্ড" (alias) করবে (L01/L06-এর সূত্র)
k = round(f_high / fs_new)
f_alias = f_high - k * fs_new
print(f"\nপ্রত্যাশিত অ্যালিয়াসড ফ্রিকোয়েন্সি: {f_high} Hz -> {f_alias} Hz (মান হিসেবে {abs(f_alias)} Hz)")

print(f"\n{'freq (Hz)':>10} | {'naive |X[k]|':>13}")
for f, m in dft_magnitude(x_naive, fs_new):
    flag = " -- ভুয়া শক্তি (৮ Hz-এর অ্যালিয়াস)" if abs(f - abs(f_alias)) < 1e-9 and m > 1 else ""
    print(f"{f:>10.2f} | {m:>13.3f}{flag}")

    
কোড চালালে দেখা যায় ডেসিমেটেড সিগন্যালের স্পেকট্রামে ৩ Hz-এ প্রকৃত সিগন্যালের চিহ্ন (ম্যাগনিচিউড ৬.০০), কিন্তু সাথে ৪ Hz-এও একটি সম্পূর্ণ ভুয়া শক্তির স্পাইক (ম্যাগনিচিউড ৪.৮০) — যা মূল সিগন্যালে কখনোই ছিল না! এটি ৮ Hz উপাদানের অ্যালিয়াস, ঠিক সূত্র অনুযায়ী গণনা করা |f_alias| = 4 Hz-এ। ডেসিমেটেড ডেটা শুধু দেখে বলার কোনো উপায় নেই এই ৪ Hz শক্তি আসলে একটি প্রকৃত ৪ Hz সিগন্যাল, নাকি একটি ৮ Hz সিগন্যালের ভুতুড়ে ছায়া — এটাই অ্যালিয়াসিং-এর বিপদ, এখন ডেসিমেশনের প্রেক্ষাপটে।

৩ · সঠিক পদ্ধতি — প্রথমে ফিল্টার, তারপর ডেসিমেট

সমাধান হলো ডেসিমেশনের আগে একটি অ্যান্টি-অ্যালিয়াস লো-পাস ফিল্টার প্রয়োগ করা, যার কাটঅফ নতুন নাইকুইস্ট সীমায় (fs_new/2 = 6 Hz) বসানো — M7 (L31)-এর windowed-sinc পদ্ধতি পুনর্ব্যবহার করে। এতে ৮ Hz উপাদানটি ডেসিমেশনের আগেই বহুলাংশে কমে যায়, ফলে ভাঁজ (fold) হওয়ার মতো তেমন কোনো শক্তি অবশিষ্ট থাকে না।

Python
def sinc_lowpass(numtaps, fc):
    """windowed-sinc লো-পাস ফিল্টার (M7/L31-এর পদ্ধতি) -- fc = কাটঅফ, fs-এর ভগ্নাংশ হিসেবে (0..0.5)"""
    M = (numtaps - 1) / 2.0
    h = []
    for n in range(numtaps):
        d = n - M
        if abs(d) < 1e-9:
            hn = 2 * fc                                   # কেন্দ্রে সীমা: sin(x)/x -> 2fc যখন x->0
        else:
            hn = math.sin(2 * math.pi * fc * d) / (math.pi * d)
        w = 0.54 - 0.46 * math.cos(2 * math.pi * n / (numtaps - 1))   # Hamming window
        h.append(hn * w)
    return h

def fir_apply(x, h):
    """ডিসক্রিট কনভোলিউশন (M3/L10) -- ফুল-লেংথ আউটপুট"""
    nx, nh = len(x), len(h)
    y = [0.0] * (nx + nh - 1)
    for n in range(nx):
        for k in range(nh):
            y[n + k] += x[n] * h[k]
    return y

numtaps = 25
fc = (fs_new / 2) / fs        # কাটঅফ = নতুন নাইকুইস্ট (৬ Hz), fs=48-এর ভগ্নাংশ হিসেবে
h = sinc_lowpass(numtaps, fc)

y_full = fir_apply(x, h)
delay = (numtaps - 1) // 2    # লিনিয়ার-ফেজ FIR-এর গ্রুপ ডিলে (M7)
y_aligned = y_full[delay: delay + N]
x_filtered_dec = y_aligned[::M]   # -- এবার ফিল্টার-করা সিগন্যাল থেকে ডেসিমেট

print(f"অ্যান্টি-অ্যালিয়াস ফিল্টার: {numtaps} ট্যাপ, কাটঅফ = {fc*fs} Hz")
print(f"\n{'freq (Hz)':>10} | {'naive |X[k]|':>13} | {'filtered |X[k]|':>16}")
mag_naive = dft_magnitude(x_naive, fs_new)
mag_filt = dft_magnitude(x_filtered_dec, fs_new)
for (f, mn), (_, mf) in zip(mag_naive, mag_filt):
    print(f"{f:>10.2f} | {mn:>13.3f} | {mf:>16.3f}")

reduction_pct = 100 * mag_filt[4][1] / mag_naive[4][1]
preserved_pct = 100 * mag_filt[3][1] / mag_naive[3][1]
print(f"\n৪ Hz ভুয়া শক্তি: naive {mag_naive[4][1]:.3f} -> filtered {mag_filt[4][1]:.3f}  ({reduction_pct:.1f}% অবশিষ্ট)")
print(f"৩ Hz প্রকৃত সিগন্যাল: naive {mag_naive[3][1]:.3f} -> filtered {mag_filt[3][1]:.3f}  ({preserved_pct:.1f}% সংরক্ষিত)")

    
সংখ্যাগুলো স্পষ্ট গল্প বলে — ফিল্টার করার পর ৪ Hz-এর ভুয়া (অ্যালিয়াসড) শক্তি ৪.৮০০ থেকে নেমে ০.৭০৩-এ (মূল মানের মাত্র ১৪.৬%, প্রায় −১৬.৭ dB হ্রাস) — অথচ প্রকৃত ৩ Hz সিগন্যাল ৬.০০০ থেকে ৫.৮০৬-এ, অর্থাৎ তার ৯৭% শক্তি অক্ষুণ্ণ থেকে গেছে। ফিল্টারটি নিখুঁত নয় (windowed-sinc-এর ট্রানজিশন ব্যান্ড আছে, M7/L34-এ বিস্তারিত), তাই সামান্য কিছু ভুয়া শক্তি রয়ে যায় — কিন্তু ফিল্টার ছাড়া ডেসিমেশনের তুলনায় এটি বহুগুণ নিরাপদ।
মূল কথা · Key takeaway

ডেসিমেশন মানে শুধু "কম স্যাম্পল রাখা" নয় — এটি কার্যকরভাবে সিগন্যালটিকে একটি নতুন, ছোট নাইকুইস্ট সীমার নিচে পুনরায় স্যাম্পল করা। M7-এর FIR লো-পাস ফিল্টারকে অ্যান্টি-অ্যালিয়াস ফিল্টার হিসেবে ব্যবহার না করলে, উচ্চ-ফ্রিকোয়েন্সি শক্তি নতুন, ভুল ফ্রিকোয়েন্সিতে ভাঁজ হয়ে ডেটাকে স্থায়ীভাবে দূষিত করে — ঠিক L06-এর অ্যালিয়াসিং-এর মতোই, কিন্তু এবার রেকর্ডিং-এর সময় নয়, প্রসেসিং পাইপলাইনের ভেতরে। L41-এ আমরা বিপরীত দিকটি দেখব — স্যাম্পল রেট বাড়ানো (আপস্যাম্পলিং), আর L42-এ দেখব কীভাবে ফিল্টার-তারপর-ডেসিমেট পদ্ধতিকে আরও কার্যকরভাবে বাস্তবায়ন করা যায় (পলিফেজ স্ট্রাকচার)।

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

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

প্র ০১ যদি মূল সিগন্যালে ৮ Hz-এর বদলে শুধু ৩ Hz উপাদান থাকত (কোনো উচ্চ-ফ্রিকোয়েন্সি উপাদান ছাড়া), তাহলে M = 4 দিয়ে নিছক x[::4] করা কি নিরাপদ হতো?

হ্যাঁ। অ্যালিয়াসিং তখনই ঘটে যখন সিগন্যালে নতুন, ছোট নাইকুইস্ট সীমা (এখানে ৬ Hz)-এর উপরে কোনো ফ্রিকোয়েন্সি উপাদান থাকে। ৩ Hz সেই সীমার অনেক নিচে, তাই এককভাবে থাকলে ফিল্টার ছাড়াই নিরাপদে ডেসিমেট করা যেত। বাস্তব সিগন্যালে সমস্যা হয় কারণ প্রায়ই অজানা বা অনিচ্ছাকৃত উচ্চ-ফ্রিকোয়েন্সি উপাদান (নয়েজ, হারমোনিক্স) উপস্থিত থাকে — তাই অ্যান্টি-অ্যালিয়াস ফিল্টার একটি নিরাপত্তামূলক অভ্যাস হিসেবে প্রায় সবসময় প্রযোজ্য হওয়া উচিত।

প্র ০২ ফিল্টার করার পরেও ৩ Hz সিগন্যালের ম্যাগনিচিউড পুরো ১০০% নয়, বরং ৯৭% কেন?

windowed-sinc ফিল্টার (M7/L31) একটি বাস্তবসম্মত, সসীম-দৈর্ঘ্যের আনুমানিকতা — এর একটি ট্রানজিশন ব্যান্ড আছে (একদম খাড়া কাটঅফ নয়), তাই কাটঅফের কাছাকাছি ফ্রিকোয়েন্সি উপাদান সামান্য কমে যায়, এবং প্যাসব্যান্ডেও সামান্য রিপল থাকতে পারে। M7/L34-এ দেখানো হয়েছে ট্যাপ-সংখ্যা বাড়ালে (দীর্ঘ ফিল্টার) এই ট্রানজিশন আরও খাড়া ও প্যাসব্যান্ড আরও সমতল হয় — এই ট্রেড-অফ প্রতিটি বাস্তব ফিল্টার ডিজাইনে উপস্থিত।

প্র ০৩ M-এর মান বাড়ালে (যেমন M=4 থেকে M=8) অ্যান্টি-অ্যালিয়াস ফিল্টারের কাটঅফ ফ্রিকোয়েন্সির কী হবে বলে আপনার ধারণা?

কাটঅফ আরও কমে যাবে। কাটঅফ সবসময় নতুন নাইকুইস্ট সীমা fs_new/2 = fs/(2M)-এ বসাতে হয় — M যত বড়, নতুন স্যাম্পল রেট তত ছোট, নাইকুইস্ট সীমাও তত ছোট। উদাহরণস্বরূপ এই ডেমোতে M=8 নিলে কাটঅফ হতো 48/16 = 3 Hz — যা আসলে প্রকৃত ৩ Hz সিগন্যালকেও আংশিকভাবে ছেঁটে ফেলতে শুরু করবে, দেখাচ্ছে কেন M-এর মান বাড়ানোর একটি বাস্তব খরচ আছে (M43-এ ব্যবহারিক ট্রেড-অফ হিসেবে আলোচিত)।

অনুশীলন

  1. চিন্তা করুন: উপরের কোডে f_high = 8.0-কে f_high = 10.0-এ বদলালে (fs=48, M=4, fs_new=12 অপরিবর্তিত রেখে) নতুন অ্যালিয়াসড ফ্রিকোয়েন্সি কত হবে? সূত্র k = round(f_high/fs_new), f_alias = f_high - k*fs_new ব্যবহার করে হিসেব করুন।

    k = round(10/12) = round(0.833) = 1, তাই f_alias = 10 - 1*12 = -2 Hz — অর্থাৎ মান হিসেবে ২ Hz-এ ভুয়া শক্তি দেখা যাবে।

  2. পরীক্ষা করুন: দ্বিতীয় কোড সেলে f_high = 8.0-কে 10.0-এ বদলে (প্রথম সেলেও একই পরিবর্তন করে) Run চেপে naive ও filtered স্পেকট্রাম তুলনা করে দেখুন ভুয়া শক্তি এবার কোন বিনে দেখা যায় এবং ফিল্টারিং তা কতটা কমায়।

    রান করলে naive স্পেকট্রামে ২ Hz বিনে একটি ভুয়া স্পাইক দেখা যাবে (ঠিক হিসেব করা f_alias মিলে যায়), এবং অ্যান্টি-অ্যালিয়াস ফিল্টারিং-এর পরে সেই স্পাইকের ম্যাগনিচিউড উল্লেখযোগ্যভাবে কমে যাবে — ঠিক যেমন মূল ডেমোতে ৪ Hz বিনে দেখা গিয়েছিল, শুধু ফ্রিকোয়েন্সি ভিন্ন।

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

আগের পাঠ
IIR ফিল্টার ইমপ্লিমেন্টেশন — ডাইরেক্ট ফর্ম I ও II