Ek-1: Sayısal Filtreler
Contents
Ek-1: Sayısal Filtreler#
Sayısal Filtreler#
Bu defterde aşağıdaki tür filtreler için bazı genel bilgiler ve uygulama örnekleri sunacağız.
FIR filtreler
IIR filtreler
Diğer defterlerde de olduğu gibi hedefimiz filtre kuramını sunmaktan çok ihtiyacınız olduğunda geri dönüp hızlıca ana kavramları tekrar etmek için okuyabileceğiniz ve kod örneklerini alıp kendi probleminize uygun şekilde değiştirerek kullanabileceğiniz bir içerik sunmak. Sayısal filtre tasarımı konusunda çok sayıda iyi kaynak zaten erişime açık bir şekilde mevcut (örnekler: Julios O. Smith III, “Introduction to Digital Filters”, J. Fessler “Design of digital filters”, Brian McFee, “Digital Signals Theory” ). Sayısal filtreden kastımızın ne olduğunu; düşük geçiren filtre, yüksek geçiren filtre, bant geçiren filtre kategorilerini bildiğinizi ve bu kitaptaki ilk 3 defteri okuduğunuzu varsayarak ilerleyeceğiz. Genel kavramları özetledikten sonra Python ile filtre tasarımı ve kullanımına odaklanacağız.
Filtre tasarımında hedef belirli bir uygulamada ihtiyaç duyulan ve uygulamadaki koşullara göre frekans cevabı tanımlanmış bir sistemin dürtü cevabının veya sistemi tanımlayan katsayıların hesaplanmasıdır.
Frekans cevabı tipik olarak; bastırılacak frekans bandında en az ne düzeyde bastırma gerektiği, geçirme bandında ne düzeyde varyasyona izin verileceği, filtrenin spektrumunda geçirme bandı ile bastırılma bandı arasında geçişin ne kadar keskin olacağı gibi kriterlerle belirlenir. Bu kriterlere uygun sistemin tasarlanması problemi birçok çalışmada ele alınmış ve artık standartlaşmış tasarım yöntemleri kullanılarak gerçekleştirilmektedir. Birçok sinyal işleme kütüphanesi (Matlab Signal Processing Toolbox, SciPy Signal Processing Module) bu standartlaşmış tasarım adımlarını işleten hazır fonksiyonlar içermektedir. Bu defterde büyük oranda tasarım için scipy.signal modülü içerisindeki hazır fonksiyonları kullanıyor ve elde ettiğimiz sistemleri örnek sinyaller üzerinde uyguluyor olacağız. Ana kavramları kısaca özetleyerek başlayalım.
2 ve 3 no’lu defterimizde bir sayısal sistemin aşağıdaki ifade ile tanımlanabileceğini görmüştük:
ve sistemin cevabını z-uzayında şu şekilde ifade etmiştik:
\(z = e^{j\omega}\) değişken dönüşümünü yaptığımızda sistemin frekans cevabını elde ederiz;
Bu sistem aşağıdaki şekilde gecikme birimleri, çarpma ve toplama birimleri içeren bir yapı olarak temsil edilebilir ve gerçeklenebilir:
Şekil Ek1.1: Direk form 1 ile sayısal sistem gerçekleme |
Burada, sağdaki bloklar \(y[n]\)’nin \(n\)’deki değerine kendi geçmiş değerlerinin katkısını gösteren geri besleme kısmını içermekte. Solda ise sadece girdi sinyali olan \(x[n]\)’in geçmiş değerlerini katsayılarla çarpıp toplayan bölümü görüyoruz.
Bu genel ifadenin özel hali olan bazı sistem kategorilerini ele almıştık. Bunların arasında geri besleme içermeyen MA (İng: Moving average) sistemler vardı:
Bu tür sistemlerde \(y[n]\) , \(x[n]\)’in o anki ve geçmiş değerlerinden oluşturuluyor ve geri besleme yok. Bu çeşit sistemleri gerçeklemek için de şöyle bir yapı kullanabiliriz.
Şekil Ek1.2: Direk form 1 ile MA sistem gerçekleme |
(AR, MA, ARMA sistemler konusunda detaylı bilgi için bakınız)
Bir MA sistem ile AR veya ARMA sistem arasındaki en temel fark MA sistemde geri beslemenin (çıktının geçmiş değerlerinin yeni çıktı oluşumuna katkısı) olmamasıdır. Geri beslemenin olup olmaması sistemin dürtü cevabının sonlu uzunlukta veya sonsuz uzunlukta olmasını (ve birçok başka özelliği) etkileyen önemli bir farklılıktır. Filtreler söz konusu olduğunda ana kategorilerimizi bu fark belirleyecek; sistemin dürtü cevabının sonlu uzunlukta olduğu ve sonsuz uzunlukta olduğu sistemleri iki yarı kategori olarak ele alıp inceleyeceğiz:
Dürtü cevabı sonlu uzunlukta filtreler (İng: FIR: finite impulse response filters)
Dürtü cevabı sonsuz uzunlukta filtreler (İng: IIR: finite impulse response filters)
FIR filtreler’i geri besleme içermeyen filtreler, IIR filtreler’i de geri besleme içeren filtreler olarak düşünebiliriz. Bu, tasarımı ve uygulamayı da farklılaştıran önemli bir farktır.
FIR filtreleri tasarlarken sistemin dürtü cevabını, ideal filtre cevabından (bir kısmını keserek/pencereleyerek) elde edeceğiz.
IIR filtreleri tasarlarken sistemin \(b\) ve \(a\) polinom katsayılarını özel yöntemlerle/tasarımlarla hesaplayacağız.
Önemli bir detay: Birçok filtre tasarım fonksiyonu kesim frekans parametresini normalize frekans değeri olarak alır. Bir örnekle açıklayalım: 10 kHz örnekleme frekansı olan bir sayısal sinyal düşünün. Bu sinyalde bulunabilecek en yüksek frekans bileşeni 5 kHz’tir ve spektrum bu sebeple 0-5kHz aralığındadır. Kesim frekansı 1 kHz olan bir düşük geçiren filtre tasarlamak istediğimizi düşünelim. Bu kesim frekansı tüm spektrum 0-1 aralığında düşünüldüğünde 0.2 normalize frekans değerine (\(f_c/f_{max} = 1 kHz/ 5 kHz= 0.2\)) karşılık gelir. Filtrelerimizi tasarlarken kesim frekansları için bu şekilde normalize frekans değerleri kullanarak tasarlayacağız. Bu şu anlamı da taşımaktadır: 0.2 normalize kesim frekansına sahip olacak şekilde tasarlanan bir düşük geçiren filtre için; filtreyi uyguladığımız sayısal sinyalin örnekleme frekansı 10 kHz ise filtre 0-1 kHz bandını geçirecek, sinyalin örnekleme frekansı 20 kHz ise filtre 0-2 kHz bandını geçirecektir. Karşılaştırmaları kolaylaştırmak için spektrum çizimlerimizde frekans eksenini normalize frekans cinsinden temsil etmeyi tercih edeceğiz.
Kullanacağımız modülleri yükleyelim. Ayrıca tasarladığımız filtrelerin spektrumlarını ve z-uzayında kutup ve sıfırlarını çizdiriyor olacağız. O fonksiyonları da diğer defterlerimizden kopyalayarak kullanalım.
import os
import numpy as np
import random
import zipfile
from scipy import signal, io
import matplotlib.pyplot as plt
import urllib.request
import soundfile as sf # Ses dosyalarını okumak için kullanacağımız kütüphane
from IPython.display import Audio
# Çizimler oluşturulurken listelenen uyarıları kapatalım
import warnings
import matplotlib.cbook
warnings.filterwarnings("ignore",category=matplotlib.cbook.mplDeprecation)
# Defter 3'ten kopyalandı
# Kutuplar ve sıfırları z-uzayında görselleştirme fonksiyonu
def z_uzayi_cizdir(kutuplar, sifirlar):
plt.figure(figsize=(4, 4))
# Birim çember çizimi
t = np.linspace(0,2*np.pi, 100)
plt.plot(np.cos(t), np.sin(t), 'k')
# Eksen doğrularının çizimi
plt.plot([0,0], [-1,1], 'k', linewidth=0.5)
plt.plot([-1,1], [0,0], 'k', linewidth=0.5)
# Sıfır ve kutupların çizimi
for kutup in kutuplar:
plt.plot(kutup.real, kutup.imag, 'x', markersize=10, color='red')
for sifir in sifirlar:
plt.plot(sifir.real, sifir.imag, 'o', markersize=10, color='red')
plt.text(-0.95, 0.95, 'z uzayı', fontsize=14)
plt.text(-0.8, -0.07, 'gerçek', fontsize=13)
plt.text(-0.15, 1.03, 'sanal', fontsize=13)
plt.grid();
# Defter 3'ten kopyalandı
# scipy.signal.freqz kullanarak frekans cevabı çizdirme fonksiyonu
def frekans_cevabi_cizdir(b, a):
# a ve b polinomları verilen bir sistemin frekans cevabının hesaplanması
w, h = signal.freqz(b, a) # w frekans değerlerini, h ise H(e^jw) degerlerini içeriyor
# frekans eksenini normalize edelim, bu karşılaştırmalarımızı kolaylaştıracak
w /= np.pi
plt.figure(figsize=(6,4));
fig, ax1 = plt.subplots();
ax1.set_title('Filtre frekans cevabı');
ax1.plot(w, 20*np.log10(abs(h)), 'b');
ax1.set_ylabel('Genlik [dB]', color='b');
ax1.set_xlabel('Normalize frekans [$\pi$ radyan/örnek]');
ax2 = ax1.twinx();
angles = np.unwrap(np.angle(h));
ax2.plot(w, angles, 'g');
ax2.set_ylabel('Açı (radyan)', color='g');
ax2.grid();
ax2.axis('tight');
plt.show();
Spektrum ve dalga formu çizim fonksiyonlarını karşılaştırmalı çizim opsiyonu sunmak için tek veya iki sinyali liste içerisinde alacak şekilde yazalım.
# Defter 2'den kopyalandı, ilk parametre değiştirildi
# Numpy.fft.fft fonksiyonu kullanarak frekans cevabı çizdiren fonksiyon (faz spektrumunu numpy.unwrap ile açar)
def spektrum_cizdir(sinyaller_listesi, frekans_nokta_sayisi=512, sadece_genlik_cizdir=False):
'''Karşılaştırmalı çizimleri kolaylaştırmak için sinyal(ler) liste olarak alınıyor.
Listede tek öğe bulunduğu durumda içeriği: [x[n]], ikinci öğe bulunduğu durumda içeriği: [x[n],y[n]] olduğu varsayılıyor
'''
x_n = sinyaller_listesi[0]
if len(sinyaller_listesi) > 1:
y_n = sinyaller_listesi[1]
else:
y_n = np.array([])
# Fourier dönüşümü bize simetrik bir sonuç vereceği için bütününü oluşturduktan sonra ilk kısmını alalım
# bu amaçla iki kat boyut kullanıp ortadan böleceğiz
X_f = np.fft.fft(x_n, frekans_nokta_sayisi * 2)
X_f = X_f[:frekans_nokta_sayisi]
# Genlik ve faz bileşenlerinin hesaplanması
X_abs = np.abs(X_f)
X_abs[X_abs < np.finfo(float).eps] = np.finfo(float).eps # log operasyonundan önce önlem
genlik_w_x = 20 * np.log10(X_abs)
faz_w_x = np.unwrap(np.angle(X_f)) # "unwrap"(katlamayı açma) işlemi eklendi
if y_n.size > 0:
Y_f = np.fft.fft(y_n, frekans_nokta_sayisi * 2)
Y_f = Y_f[:frekans_nokta_sayisi]
Y_abs = np.abs(Y_f)
Y_abs[Y_abs < np.finfo(float).eps] = np.finfo(float).eps # log operasyonundan önce önlem
genlik_w_y = 20 * np.log10(Y_abs);faz_w_y = np.unwrap(np.angle(Y_f))
# Frekans ekseninde normalize frekans değerleri serisi
f_normalize = np.arange(0, frekans_nokta_sayisi) / frekans_nokta_sayisi
# Spektrum çizdirme adımları
plt.figure(figsize=(12,3))
if sadece_genlik_cizdir:
plt.title('Genlik spektrumu')
plt.plot(f_normalize, genlik_w_x, 'b', label='|X($\omega$)|(dB)')
plt.ylabel('|X($\omega$)|(dB)', color='b')
plt.xlabel('Normalize frekans [$\pi$ radyan/örnek]')
plt.grid()
if y_n.size > 0:
plt.plot(f_normalize, genlik_w_y, 'r', label='|Y($\omega$)|(dB)')
plt.legend()
# görselleri iyileştirmek için eksen sınırlarının kontrolü
plt.ylim(max(-60, min(np.min(genlik_w_x), np.min(genlik_w_y))), max(np.max(genlik_w_x), np.max(genlik_w_y)))
else:
# görselleri iyileştirmek için eksen sınırlarının kontrolü
plt.ylim(max(-60, np.min(genlik_w_x)), np.max(genlik_w_x))
else:
plt.subplot(1,2,1)
plt.title('Genlik spektrumu')
plt.plot(f_normalize, genlik_w_x, 'b')
plt.ylabel('|X($\omega$)|(dB)', color='b')
plt.xlabel('Normalize frekans [$\pi$ radyan/örnek]')
plt.grid()
if y_n.size > 0:
plt.plot(f_normalize, genlik_w_y, 'r', label='|Y($\omega$)|(dB)')
plt.legend();
plt.subplot(1,2,2)
plt.title('Faz spektrumu')
plt.plot(f_normalize, faz_w_x, 'b', label='faz(X($\omega$))')
plt.ylabel('faz(X($\omega$)) (radyan)', color='b')
plt.xlabel('Normalize frekans [$\pi$ radyan/örnek]')
plt.grid();
if y_n.size > 0:
plt.plot(f_normalize, faz_w_y, 'r', label='faz(Y($\omega$))')
plt.legend();
plt.show();
def dalga_formu_cizdir(sinyaller_listesi, ornekleme_fr):
'''Karşılaştırmalı çizimleri kolaylaştırmak için sinyal(ler) liste olarak alınıyor.
Listede tek öğe bulunduğu durumda içeriği: [x[n]], ikinci öğe bulunduğu durumda içeriği: [x[n],y[n]] olduğu varsayılıyor
'''
x_n = sinyaller_listesi[0]
if len(sinyaller_listesi) > 1:
y_n = sinyaller_listesi[1]
else:
y_n = np.array([])
zaman_serisi = np.arange(0, x_n.shape[0]/ornekleme_fr, 1/ ornekleme_fr)
if zaman_serisi.shape[0] > x_n.shape[0]:
zaman_serisi = zaman_serisi[:x_n.shape[0]]
plt.figure(figsize=(12, 4))
plt.plot(zaman_serisi, x_n, 'b', label='x[n]');
if y_n.size > 0:
plt.plot(zaman_serisi, y_n, 'r', label='y[n]');
plt.legend()
plt.xlabel('Zaman (saniye)'); plt.ylabel('sinyal genliği');
Filtre uygulamalarından kullanılacak test sinyallerinin yüklenmesi#
Şimdi deneylerde kullanmak üzere bir dizi test sinyali yükleyelim.
Test sinyali 1 : Sentetik harmonik sinyal
İlk test sinyalimiz bu defterin en sonunda sunulan kodla oluşturulmuş temel titreşim frekansının tam sayılı katlarında harmonikler içeren bir sentetik sinyal. Genlik spektrumunu(Şekil Ek1.4) incelediğinizde harmonikleri gözleyebilirsiniz.
# Test sinyali 1: Sentetik harmonik sinyal (sinyali oluşturan kod için defterin sonuna bakınız)
url = 'https://raw.githubusercontent.com/barisbozkurt/dataDumpForCourses/master/harmonic_signal.txt'
urllib.request.urlretrieve(url,'harmonic_signal.txt')
test_sinyali_1 = np.loadtxt('harmonic_signal.txt')
test_sinyali_1_ornekleme_fr = 10000 # Örnekleme frekansı Hz
dalga_formu_cizdir([test_sinyali_1], test_sinyali_1_ornekleme_fr)
plt.title('Test sinyali 1 (Sentetik harmonik sinyal)');

Şekil Ek1.3: Test sinyali 1 dalga formu
spektrum_cizdir([test_sinyali_1], sadece_genlik_cizdir=True)

Şekil Ek1.4: Test sinyali 1 genlik spektrumu
Test sinyali 2: EKG sinyali
MIT-Biomedical Signal and Image Processing Labs , Lab-1 ECG sinyal işleme, “3.2 Week 1: Signal Conditioning/Noise Reduction” başlıklı lab dökümanında paylaşılan EKG sinyali.
url = 'https://ocw.mit.edu/courses/hst-582j-biomedical-signal-and-image-processing-spring-2007/8900fe7d215ec3068342f2b91ecbe501_lab1files.zip'
dosya_ismi = 'lab1files.zip'
urllib.request.urlretrieve(url,dosya_ismi)
# zip pakedinin açılması
zip_ref = zipfile.ZipFile(dosya_ismi, 'r')
zip_ref.extractall()
zip_ref.close()
mat = io.loadmat('lab1files/week1/normal.mat')
zaman_serisi = mat['normal'][:,0]
ekg_sinyal = mat['normal'][:,1]
# Soru tanımında 5-10 saniye uzunluğunda sinyal alınması önerilmiş
# Baştan 8 saniyelik kısmı alalım
sure = 8
test_sinyali_2 = ekg_sinyal[zaman_serisi < sure]
zaman_serisi_2 = zaman_serisi[zaman_serisi < sure]
# Örnekleme periyodunu ardışık iki örnek arasındaki zaman farkı olarak bulabiliriz
# float kullanıldığı için tek bir fark değeri kullanmak yerine farkların ortalamasını kullanmak daha güvenli olabilir
test_sinyali_2_ornekleme_periodu = np.mean(np.diff(zaman_serisi));
test_sinyali_2_ornekleme_fr = int(1 / test_sinyali_2_ornekleme_periodu)
print('Örnekleme frekansı: ', test_sinyali_2_ornekleme_fr, 'Hz')
Örnekleme frekansı: 249 Hz
dalga_formu_cizdir([test_sinyali_2], test_sinyali_2_ornekleme_fr)
plt.title('Test sinyali 2 (EKG)');

Şekil Ek1.5: Test sinyali 2 dalga formu
Sinyalin dalga formunu incelediğimizde EKG için beklenilen dalga formu bileşenlerini görmekle beraber yaklaşık 4 saniye periyodlu bir düşük frekans bileşeni gözlüyoruz. Bu bileşeni bir çeşit gürültü olarak düşünebiliriz.
spektrum_cizdir([test_sinyali_2], sadece_genlik_cizdir=True)

Şekil Ek1.6: Test sinyali 2 genlik spektrumu
Test sinyali 3: Müzik sinyali
Test sinyali 3, çok enstrümanlı bir kayıt. Yapacağımız filtre uygulamalarında sonucu dinleyerek karşılaştırma imkanı tanıyacağı için bu sinyali tercih ettik.
%%capture
# John Scofield Band, Überjam albümünde bir kesit
url = 'https://archive.org/download/cd_uberjam_john-scofield-band/disc1/02.%20John%20Scofield%20Band%20-%20Ideofunk_sample.mp3'
urllib.request.urlretrieve(url,'funk_sample.mp3');
# mp3 -> wav dönüşüm işlemi
os.system('ffmpeg -i funk_sample.mp3 -vn -acodec pcm_s16le -ac 1 -ar 44100 -f wav funk_sample.wav');
test_sinyali_3, test_sinyali_3_ornekleme_fr = sf.read('funk_sample.wav')
test_sinyali_3 = test_sinyali_3 / np.max(np.abs(test_sinyali_3)) # genlik normalizasyonu
ffmpeg version 4.2.7-0ubuntu0.1 Copyright (c) 2000-2022 the FFmpeg developers
built with gcc 9 (Ubuntu 9.4.0-1ubuntu1~20.04.1)
configuration: --prefix=/usr --extra-version=0ubuntu0.1 --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --arch=amd64 --enable-gpl --disable-stripping --enable-avresample --disable-filter=resample --enable-avisynth --enable-gnutls --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libcodec2 --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libjack --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librsvg --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzmq --enable-libzvbi --enable-lv2 --enable-omx --enable-openal --enable-opencl --enable-opengl --enable-sdl2 --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-nvenc --enable-chromaprint --enable-frei0r --enable-libx264 --enable-shared
WARNING: library configuration mismatch
avcodec configuration: --prefix=/usr --extra-version=0ubuntu0.1 --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --arch=amd64 --enable-gpl --disable-stripping --enable-avresample --disable-filter=resample --enable-avisynth --enable-gnutls --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libcodec2 --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libjack --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librsvg --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzmq --enable-libzvbi --enable-lv2 --enable-omx --enable-openal --enable-opencl --enable-opengl --enable-sdl2 --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-nvenc --enable-chromaprint --enable-frei0r --enable-libx264 --enable-shared --enable-version3 --disable-doc --disable-programs --enable-libaribb24 --enable-liblensfun --enable-libopencore_amrnb --enable-libopencore_amrwb --enable-libtesseract --enable-libvo_amrwbenc
libavutil 56. 31.100 / 56. 31.100
libavcodec 58. 54.100 / 58. 54.100
libavformat 58. 29.100 / 58. 29.100
libavdevice 58. 8.100 / 58. 8.100
libavfilter 7. 57.100 / 7. 57.100
libavresample 4. 0. 0 / 4. 0. 0
libswscale 5. 5.100 / 5. 5.100
libswresample 3. 5.100 / 3. 5.100
libpostproc 55. 5.100 / 55. 5.100
Input #0, mp3, from 'funk_sample.mp3':
Metadata:
title : Ideofunk
artist : John Scofield Band
track : 2/11
album : Überjam
disc : 1/1
date : 2002
comment : https://archive.org/details/cd_uberjam_john-scofield-band
TLEN : 283.69
album_artist : John Scofield Band
encoder : LAME 64bits version 3.99.5 (http://lame.sf.net)
Duration: 00:00:30.04, start: 0.025056, bitrate: 193 kb/s
Stream #0:0: Audio: mp3, 44100 Hz, stereo, fltp, 163 kb/s
Metadata:
encoder : LAME3.99r
Side data:
replaygain: track gain - -5.400000, track peak - unknown, album gain - unknown, album peak - unknown,
Stream #0:1: Video: mjpeg (Baseline), yuvj420p(pc, bt470bg/unknown/unknown), 640x638 [SAR 1:1 DAR 320:319], 90k tbr, 90k tbn, 90k tbc (attached pic)
Metadata:
comment : Other
File 'funk_sample.wav' already exists. Overwrite ? [y/N] Not overwriting - exiting
Audio(test_sinyali_3, rate=test_sinyali_3_ornekleme_fr)