Podręcznik
2. Sygnały i ich charakterystyki
2.8. Reprezentacja sygnałów w języku Python
Celem poniższych ćwiczeń jest zapoznanie się z wybranymi typami sygnałów oraz nabycie praktycznych umiejętności w generowaniu i wizualizacji ich przebiegów, a także poznanie podstawowych funkcji i komend w języku Python przydatnych w analizie sygnałów.
Poniższe przykłady przedstawiają pełne kody źródłowe w języku Python, opracowane na potrzeby realizacji poszczególnych zadań. Do wykonania przedstawionych zadań wymagane jest wykorzystanie następujących bibliotek języka Python:
import numpy as np # obliczenia numeryczne i tablice
import matplotlib.pyplot as plt # tworzenie wykresów ciągłych i dyskretnych
import scipy.signal as signal # generowanie standardowych sygnałów,
# takich jak np. prostokątny, trójkątny, piłokształtny
oraz wymagana jest znajomość między innymi następujących poleceń i funkcji języka Python:
np.arange, np.linspace # tworzenie wektorów czasu
plt.figure, plt.plot, plt.stem # rysowanie wykresów ciągłych i dyskretnych
plt.grid, plt.xlabel, plt.ylabel, plt.title, plt.legend # oznaczanie osi, tytułów, legend i siatki
np.sin, np.exp, np.pi # generowanie sygnałów sinusoidalnych, wykładniczych, stała pi
signal.square, signal.sawtooth, signal.chirp # generowanie sygnałów specjalnych
W zadaniach będziemy łączyć te funkcje w celu generowania, wizualizacji i analizy różnych typów sygnałów. Poniżej znajdują się ćwiczenia z przykładową implementacją. Autor zachęca czytelnika do samodzielnego uruchamiania kodów oraz do eksperymentowania z różnymi parametrami sygnałów poprzez ich modyfikację.
Wygenerować skok jednostkowy określony przez następujące równanie:
Narysować sygnał ciągły \(x(t)\) i jego wersję dyskretną \(x[n]\) dla \(N = 30\) próbek przy częstotliwości próbkowania \(f_s = 1\) Hz.
import numpy as np
import matplotlib.pyplot as plt
# Parametry sygnału
fs = 1 # częstotliwość próbkowania [Hz]
T = 1 / fs # okres próbkowania
n = np.arange(-4, 26) # 30 próbek: od -4 do 25
# Sygnał dyskretny: skok jednostkowy
x_disc = np.where(n >= 10, 1, 0)
# Sygnał ciągły (z większą rozdzielczością)
t_cont = np.linspace(-4, 25, 1000)
x_cont = np.where(t_cont >= 10, 1, 0)
# Tworzenie dwóch wykresów
fig, axs = plt.subplots(2, 1, figsize=(10, 6), sharex=True)
# Wykres 1: sygnał ciągły
axs[0].plot(t_cont, x_cont)
axs[0].set_title(r'(a) Skok jednostkowy – sygnał ciągły: $x(t)$')
axs[0].set_ylabel("Amplituda")
axs[0].grid(True)
# Wykres 2: sygnał dyskretny
axs[1].stem(n, x_disc, linefmt='r-', markerfmt='ro', basefmt='k-')
axs[1].set_title(r'(b) Skok jednostkowy – sygnał dyskretny: $x[n]$')
axs[1].set_xlabel('Czas [s]')
axs[1].set_ylabel('Amplituda')
axs[1].grid(True)
plt.tight_layout()
plt.show()

Wygenerować sygnał sinusoidalny określony przez następujące równanie:
o amplitudzie \( A = 5 \), częstotliwości sygnału \( f = 100 \) Hz i przesunięciu fazowym \( 45^{\circ} \). Narysować sygnał ciągły \(x(t)\) i jego wersję dyskretną \(x[n]\) dla \( N = 25 \) próbek, przyjmując częstotliwość próbkowania \( f_s =1000 \) Hz. Dodatkowo, sporządzić wspólny wykres sygnału ciągłego i dyskretnego na jednym rysunku.
import numpy as np
import matplotlib.pyplot as plt
# Parametry sygnału
fs = 1000 # częstotliwość próbkowania [Hz]
N = 25 # liczba próbek
A = 5 # amplituda
f = 100 # częstotliwość sygnału [Hz]
T = 1 / fs # okres próbkowania
phi = np.pi / 4 # przesunięcie fazowe [rad]
# Oś czasu: ciągła i dyskretna
t_cont = np.linspace(0, (N - 1) / fs, 1000)
n = np.arange(N)
t_disc = n * T
# Sygnały
x_cont = A * np.sin(2 * np.pi * f * t_cont + phi) # sygnał ciągły
x_disc = A * np.sin(2 * np.pi * f * t_disc + phi) # sygnał dyskretny
# Tworzenie dwóch wykresów
fig, axs = plt.subplots(2, 1, figsize=(10, 6), sharex=True)
# Wykres 1: sygnał ciągły
axs[0].plot(t_cont, x_cont)
axs[0].set_title(r'(a) Sygnał sinusoidalny ciągły: $x(t) =5\sin(2\pi f t + \frac{\pi}{4})$')
axs[0].set_ylabel('Amplituda')
axs[0].grid(True)
# Wykres 2: sygnał dyskretny
axs[1].stem(t_disc, x_disc, linefmt='r-', markerfmt='ro', basefmt='k-')
axs[1].set_title(r'(b) Sygnał sinusoidalny dyskretny: $x[n] = 5\sin(2\pi f nT + \frac{\pi}{4})$')
axs[1].set_xlabel('Czas [s]')
axs[1].set_ylabel('Amplituda')
axs[1].grid(True)
plt.tight_layout()
plt.show()

import numpy as np
import matplotlib.pyplot as plt
# Parametry sygnału
fs = 1000 # częstotliwość próbkowania [Hz]
N = 25 # liczba próbek
A = 5 # amplituda
f = 50 # częstotliwość sygnału [Hz]
T = 1 / fs # okres próbkowania
phi = np.pi / 4 # przesunięcie fazowe [rad]
# Oś czasu: ciągła i dyskretna
t_cont = np.linspace(0, (N - 1) / fs, 1000)
n = np.arange(N)
t_disc = n * T
# Sygnały
x_cont = A * np.sin(2 * np.pi * f * t_cont + phi) # sygnał ciągły
x_disc = A * np.sin(2 * np.pi * f * t_disc + phi) # sygnał dyskretny
# Wykres
plt.figure(figsize=(10, 4))
plt.plot(t_cont, x_cont, label=r'Sygnał ciągły $x(t)$', linewidth=2)
plt.stem(t_disc, x_disc, linefmt='r-', markerfmt='ro', basefmt='k-', label=r'Sygnał dyskretny $x[n]$')
plt.title(r'Sygnał sinusoidalny: $x(t) = 5\sin(2\pi f t + \frac{\pi}{4})$')
plt.xlabel('Czas [s]')
plt.ylabel('Amplituda')
plt.grid(True)
plt.legend()
plt.tight_layout()
plt.show()

Wygenerować sygnał sinusoidalny tłumiony wykładniczo opisany równaniem:
dla amplitudy \( A = 10 \), częstotliwości sygnału sinusoidalnego \( f = 2 \) Hz i współczynnika tłumienia \(\alpha = 0.5 \). Narysować sygnał ciągły \( x(t) \) i jego wersję dyskretną \( x[n] \) dla \( N = 100 \) próbek, przyjmując częstotliwość próbkowania \( f_s = 20 \) Hz.
import numpy as np
import matplotlib.pyplot as plt
# Parametry sygnału
A = 10 # amplituda
f = 2 # częstotliwość sinusoidy [Hz]
alpha = 0.5 # tłumienie wykładnicze [1/s]
N = 100 # liczba próbek
fs = 20 # częstotliwość próbkowania [Hz]
T = 1 / fs # okres próbkowania
# Sygnał ciągły
t_cont = np.linspace(0, (N - 1) * T, 1000)
x_cont = A * np.exp(-alpha * t_cont) * np.sin(2 * np.pi * f * t_cont)
# Sygnał dyskretny
n = np.arange(N)
t_disc = n * T
x_disc = A * np.exp(-alpha * t_disc) * np.sin(2 * np.pi * f * t_disc)
# Tworzenie dwóch wykresów
fig, axs = plt.subplots(2, 1, figsize=(10, 8), sharex=True)
# Wykres 1: sygnał ciągły
axs[0].plot(t_cont, x_cont, color='blue')
axs[0].set_title(r'(a) Sygnał ciągły: $x(t) = 10e^{-0.5t}\sin(2\pi f t)$', fontsize=12)
axs[0].set_ylabel('Amplituda')
axs[0].grid(True)
# Wykres 2: sygnał dyskretny
axs[1].stem(t_disc, x_disc, linefmt='r-', markerfmt='ro', basefmt='k-')
axs[1].set_title(r'(b) Sygnał dyskretny: $x[n] = 10e^{-0.5nT}\sin(2\pi f nT)$', fontsize=12)
axs[1].set_xlabel('Czas [s]')
axs[1].set_ylabel('Amplituda')
axs[1].grid(True)
plt.tight_layout()
plt.show()

Wygenerować sygnał wykładniczy narastający określony przez następujące równanie:
Narysować na jednym wykresie sygnał ciągły \( x(t) \) oraz jego wersję dyskretną \( x[n] \) dla \( N = 50 \) próbek, przy częstotliwości próbkowania \( f_s = 5\text{ Hz} \).
import numpy as np
import matplotlib.pyplot as plt
# Parametry sygnału
fs = 5 # częstotliwość próbkowania [Hz]
N = 50 # liczba próbek
alpha = -0.5 # wykładnik
T = 1 / fs # okres próbkowania
t_max = (N - 1) / fs
# Czas ciągły i dyskretny
t_cont = np.linspace(0, t_max, 1000)
n = np.arange(N)
t_disc = n * T
x_cont = (1 - np.exp(alpha * t_cont))
x_disc = (1 - np.exp(alpha * t_disc))
# Wspólny wykres
plt.figure(figsize=(10, 4))
plt.plot(t_cont, x_cont, label=r'Sygnał ciągły $x(t)$', linewidth=2)
plt.stem(t_disc, x_disc, linefmt='r-', markerfmt='ro', basefmt='k-', label=r'Sygnał dyskretny $x[n]$')
plt.title(r'Sygnał wykładniczy narastający: $x(t) = \left(1 - 2e^{-0.5t}\right)$')
plt.xlabel('Czas [s]')
plt.ylabel('Amplituda')
plt.grid(True)
plt.legend()
plt.tight_layout()
plt.show()

Wygenerować sygnał \(Sinc \) opisany równaniem:
dla częstotliwości sygnału sinusoidalnego \( f = 1 \) Hz. Narysować wspólny wykres sygnału ciągłego \(x(t)\) i dyskretnego \(x[n]\) dla \(N = 100\) próbek, przyjmując częstotliwość próbkowania \(f_s = 5\) Hz.
import numpy as np
import matplotlib.pyplot as plt
# Parametry sygnału
f = 1 # częstotliwość sygnału
fs = 5 # częstotliwość próbkowania
N = 100 # liczba próbek
T = 1 / fs
# Oś czasu - ciągła i dyskretna
t_cont = np.linspace(-10, 10, 1000)
n = np.arange(-N//2, N//2)
t_disc = n * T
# Funkcja Sinc - ciągła i dyskretna
y_cont = np.sinc(f * t_cont)
y_disc = np.sinc(f * t_disc)
# Wspólny wykres
plt.figure(figsize=(10, 5))
# Wersja ciągła i dyskretna
plt.plot(t_cont, y_cont, label=r'Sygnał ciągły $x(t)$')
plt.stem(t_disc, y_disc, linefmt='r-', markerfmt='ro', basefmt='k-', label=r'Sygnał dyskretny $x[n]$')
plt.title(r'Funkcja $Sinc$')
plt.xlabel('Czas [s]')
plt.ylabel('Amplituda')
plt.grid(True)
plt.legend()
plt.tight_layout()
plt.show()

Wygenerować sygnały prostokątny, trójkątny oraz piłokształtny dla częstotliwości \(f = 5\ \text{Hz}\). Dla każdego sygnału narysować wersję ciągłą \(x(t)\) i dyskretną \(x[n]\) dla \(N = 200\) próbek, przyjmując częstotliwość próbkowania \(f_s = 100 \) Hz.
import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
# Parametry sygnałów
fs = 100 # częstotliwość próbkowania [Hz]
f = 5 # częstotliwość sygnału [Hz]
N = 200 # liczba próbek
T = N / fs # czas trwania sygnału [s]
# Oś czasu
t = np.linspace(0, T, N, endpoint=False)
# Sygnał prostokątny
rect_signal = signal.square(2 * np.pi * f * t, duty=0.5) # duty musi mieścić się w [0,1].
# Sygnał trójkątny
tri_signal = signal.sawtooth(2 * np.pi * f * t, width=0.5) # width=0.5 daje trójkąt
# Sygnał piłokształtny (piłowy)
saw_signal = signal.sawtooth(2 * np.pi * f * t, width=1) # width=1 daje piłę
# Rysowanie wykresów
plt.figure(figsize=(12, 8))
plt.subplot(3,1,1)
plt.plot(t, rect_signal, label='Sygnał prostokątny')
plt.title('Sygnał prostokątny')
plt.xlabel('Czas [s]')
plt.ylabel('Amplituda')
plt.grid(True)
plt.subplot(3,1,2)
plt.plot(t, tri_signal, label='Sygnał trójkątny', color='orange')
plt.title('Sygnał trójkątny')
plt.xlabel('Czas [s]')
plt.ylabel('Amplituda')
plt.grid(True)
plt.subplot(3,1,3)
plt.plot(t, saw_signal, label='Sygnał piłokształtny', color='green')
plt.title('Sygnał piłokształtny')
plt.xlabel('Czas [s]')
plt.ylabel('Amplituda')
plt.grid(True)
plt.tight_layout()
plt.show()

import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
# Parametry sygnałów
fs = 100 # częstotliwość próbkowania [Hz]
f = 5 # częstotliwość sygnału [Hz]
N = 200 # liczba próbek
T = N / fs # czas trwania sygnału [s]
# Oś czasu dyskretna
n = np.arange(N)
t = n / fs
# Sygnały dyskretne
rect_signal = signal.square(2 * np.pi * f * t, duty=0.5)
tri_signal = signal.sawtooth(2 * np.pi * f * t, width=0.5)
saw_signal = signal.sawtooth(2 * np.pi * f * t, width=1)
# Rysowanie wykresów
plt.figure(figsize=(12, 8))
plt.subplot(3,1,1)
plt.stem(t, rect_signal, linefmt='b-', markerfmt='bo', basefmt='k-')
plt.title('Dyskretny sygnał prostokątny')
plt.xlabel('Czas [s]')
plt.ylabel('Amplituda')
plt.grid(True)
plt.subplot(3,1,2)
plt.stem(t, tri_signal, linefmt='orange', markerfmt='o', basefmt='k-')
plt.title('Dyskretny sygnał trójkątny')
plt.xlabel('Czas [s]')
plt.ylabel('Amplituda')
plt.grid(True)
plt.subplot(3,1,3)
plt.stem(t, saw_signal, linefmt='green', markerfmt='o', basefmt='k-')
plt.title('Dyskretny sygnał piłokształtny')
plt.xlabel('Czas [s]')
plt.ylabel('Amplituda')
plt.grid(True)
plt.tight_layout()
plt.show()

Wygenerować sygnał świergotowy (chirp) o częstotliwości liniowo rosnącej od \(1\) Hz do \(400\) Hz. Przyjąć czas trwania sygnału \(0.1\) sekundy oraz częstotliwość próbkowania \(f_s = 10\ 000\) Hz. Narysować wykres sygnału ciągłego \(x(t)\).
import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import chirp
# Parametry
t = np.linspace(0, 0.1, 10000) # czas trwania 0.1 [s]
# Generowanie sygnału świergotowego liniowego od 1 do 400 Hz
x = chirp(t, f0=1, f1=400, t1=0.1, method='linear')
# f0 = 1 częstotliwość początkowa [Hz]
# f1 = 400 częstotliwość końcowa [Hz]
# t1 = 0.1 czas, w którym osiągana jest f1
plt.figure(figsize=(10, 3))
plt.plot(t, x)
plt.title('Sygnał świergotowy liniowy, 1-400 Hz')
plt.xlabel('Czas [s]')
plt.ylabel('Amplituda')
plt.grid(True)
plt.tight_layout()
plt.show()

Sygnał świergotowy to sygnał o zmiennej częstotliwości chwilowej. Najprostszym jego przypadkiem jest sygnał sinusoidalny, którego częstotliwość chwilowa jest przestrajana w trakcie generowania. Przestrajanie częstotliwości odbywa się zwykle w sposób liniowy lub logarytmiczny. Sygnały świergotowe stosuje się m.in. do analizy charakterystyk częstotliwościowych urządzeń (np. filtrów, wzmacniaczy, głośników), do wyznaczania odpowiedzi systemu na różne częstotliwości w krótkim czasie, a także do badania reakcji układów biologicznych na bodźce o zmiennej częstotliwości.
Po zapoznaniu się z możliwościami generowania podstawowych sygnałów, możemy prześledzić operację splotu oraz zagadnienia związane z wyznaczaniem odpowiedzi impulsowej systemów LTI.
Wymagana jest znajomość funkcji:
np.convolve # obliczanie splotu dwóch jednowymiarowych sygnałów dyskretnych
# funkcja z biblioteki NumPy, przeznaczona głównie do prostych operacji na wektorach
convolve # obliczanie splotu sygnałów (1D, 2D i wyższych wymiarów)
# funkcja z biblioteki scipy.signal, oferująca większe możliwości
dzięki którym można łatwo wykonać operację splotu, np. w celu wyznaczenia odpowiedzi impulsowej systemów LTI.
Obliczyć splot dwóch ciągów próbek sygnałów: \( X = [2,4,6] \) oraz \( Y = [1,2] \).
Zgodnie z zasadą obliczenia splotu, opisaną w rozdziale 2.4, najpierw odwracamy ciąg próbek \( Y \) w czasie i wyrównujemy go z ciągiem próbek \( X \) tak, aby ostatnia próbka \( Y \) po odwróceniu pokrywała się z pierwszą próbką \( X\). Następnie mnożymy przez siebie pokrywające się próbki i sumujemy wyniki, co daje pierwszą wartość splotu.
X 2 4 6 2 * 1 = 2
Y 2 1 splot = [2]
Odwrócony ciąg próbek \( Y \) przesuwamy w prawo o jedno miejsce i powtarzamy operację:
X 2 4 6 2 * 2 + 4 * 1 = 8
Y 2 1 splot = [2 8]
Następnie przesuwamy ciąg próbek \( Y \) ponownie:
X 2 4 6 4 * 2 + 6 * 1 = 14
Y 2 1 splot = [2 8 14]
Jeżeli żadne próbki sygnałów nie pokrywają się, to kończymy obliczenia:
X 2 4 6 6 * 2 = 12
Y 2 1 splot = [2 8 14 12]
Operacja splotu jest przemienna, co oznacza, że otrzymamy ten sam wynik, jeśli odwrócimy próbki \( X \) i przesuwamy próbki \( Y \). Taki wynik możemy uzyskać w Pythonie:
import numpy as np
X = [2, 4, 6]
Y = [1, 2]
splot = np.convolve(X, Y)
print('Wynik splotu:', splot)
import numpy as np import matplotlib.pyplot as plt from scipy.signal import convolve # obliczanie splotu dwóch sygnałów # Definicja impulsu prostokątnego def rect(t): return np.where(np.abs(t) <= 0.5, 1, 0) # Osie czasu t = np.linspace(-2, 2, 400) dt = t[1] - t[0] # Sygnały x = rect(t) # impuls prostokątny h = rect(t - 0.5) # przesunięty impuls prostokątny # Splot ciągły (jako dyskretny z krokiem dt) y = convolve(x, h, mode='full') * dt t_y = np.linspace(t[0]+t[0], t[-1]+t[-1], len(y)) # Wykresy plt.figure(figsize=(10,6)) plt.plot(t, x, label='$x(t)$') plt.plot(t, h, label='$h(t)$') plt.plot(t_y, y, label='$y(t) = x(t)*h(t)$') plt.title('Splot sygnałów prostokątnych') plt.xlabel('Czas [s]') plt.ylabel('Amplituda') plt.legend() plt.grid(True) plt.tight_layout() plt.show()

import numpy as np import matplotlib.pyplot as plt from scipy.signal import convolve # Parametry fs = 1000 # częstotliwość próbkowania [Hz] T = 1 / fs # okres próbkowania t = np.arange(0, 1, T) # oś czasu dla 1 sekundy # Częstotliwości sygnałów f1 = 5 # Hz f2 = 10 # Hz x = np.sin(2 * np.pi * f1 * t) h = np.sin(2 * np.pi * f2 * t) # Splot dyskretny y = convolve(x, h, mode='full') * T t_conv = np.arange(0, len(y)) * T # oś czasu dla splotu # Wykresy plt.figure(figsize=(12, 6)) # Wykres 1: oba sygnały wejściowe plt.subplot(2, 1, 1) plt.plot(t, x, label=r'$x(t) = sin(2\pi \cdot 5t)$') plt.plot(t, h, label=r'$h(t) = sin(2\pi \cdot 10t)$', linestyle='--') plt.title('Sygnały sinusoidalne') plt.xlabel('Czas [s]') plt.ylabel('Amplituda') plt.grid(True) plt.legend(loc='upper right') # Wykres 2: wynik splotu plt.subplot(2, 1, 2) plt.plot(t_conv, y, label=r'splot $x(t) * h(t)$', color='green') plt.title('Wynik splotu') plt.xlabel('Czas [s]') plt.ylabel('Amplituda') plt.grid(True) plt.legend(loc='upper right') plt.tight_layout() plt.show()

Wyznaczyć odpowiedź systemu LTI na sygnał wejściowy – impuls prostokątny, trwający \( 1 \) s, jeśli odpowiedź impulsowa systemu jest sygnałem wykładniczym malejącym:
Przypomnijmy, że odpowiedź systemu LTI na dowolny sygnał wejściowy jest splotem sygnału wejściowego z odpowiedzią impulsową.
import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import convolve
# Parametry
fs = 1000 # częstotliwość próbkowania
T = 1 / fs # okres próbkowania
t = np.arange(0, 5, T) # od 0 do 5 s
# Sygnał wejściowy: impuls prostokątny trwający 1 [s]
x = np.where(t < 1, 1, 0)
# Odpowiedź impulsowa: sygnał wykładniczy malejąca
h = np.exp(-t)
# Splot
y = convolve(x, h, mode='full') * T
t_conv = np.arange(0, len(y)) * T
# Wykresy
plt.figure(figsize=(12, 7))
plt.subplot(3, 1, 1)
plt.plot(t, x, label=r'$x(t)$: impuls prostokątny')
plt.title(r'Sygnał wejściowy $x(t)$')
plt.xlabel('Czas [s]')
plt.ylabel('Amplituda')
plt.grid(True)
plt.legend()
plt.subplot(3, 1, 2)
plt.plot(t, h, label=r'$h(t) = e^{-t}$', color='orange')
plt.title(r'Odpowiedź impulsowa $h(t)$')
plt.xlabel('Czas [s]')
plt.ylabel('Amplituda')
plt.grid(True)
plt.legend()
plt.subplot(3, 1, 3)
plt.plot(t_conv, y, label=r'$y(t) = x(t) * h(t)$', color='green')
plt.title('Odpowiedź systemu LTI: wynik splotu')
plt.xlabel('Czas [s]')
plt.ylabel('Amplituda')
plt.grid(True)
plt.legend()
plt.tight_layout()
plt.show()
