Александр Иванов – Цифровая обработка сигналов на Python. От инженера к разработчику. (страница 4)
Для речи высокая частота дискретизации не нужна. Человеческий голос редко поднимается выше 8 000 Гц, а основные частоты, важные для разборчивости, находятся ниже 4 000 Гц. Поэтому телефонные разговоры оцифровывают с частотой 8 000 Гц — этого достаточно, чтобы понять собеседника, хотя качество заметно хуже, чем у музыки. Для подкастов и аудиокниг часто используют 22 050 Гц — половину от CD-качества. Такой выбор экономит место на диске и ускоряет обработку, не жертвуя качеством восприятия речи.
Давайте проверим всё это кодом. Создадим простой синусоидальный сигнал — чистый тон определённой частоты — и попробуем оцифровать его с разной частотой дискретизации.
python
import numpy as np
import soundfile as sf
import matplotlib.pyplot as plt
# Параметры сигнала
duration = 1.0 # одна секунда
freq = 440.0 # нота "ля" первой октавы
# Создаём непрерывное время с очень высокой частотой дискретизации
# Это наша "идеальная" модель аналогового сигнала
sr_continuous = 441000 # в 10 раз выше, чем CD
t_continuous = np.linspace(0, duration, int(sr_continuous * duration), endpoint=False)
y_continuous = np.sin(2 * np.pi * freq * t_continuous)
print(f"Идеальный сигнал: {len(y_continuous)} отсчётов")
print(f"Частота сигнала: {freq} Гц")
# Теперь симулируем дискретизацию с разными частотами
sample_rates = [8000, 11025, 22050, 44100]
for sr in sample_rates:
# Берём каждый n-ный отсчёт из идеального сигнала
step = sr_continuous // sr
y_sampled = y_continuous[::step]
t_sampled = t_continuous[::step]
print(f"\nЧастота дискретизации: {sr} Гц")
print(f" Количество отсчётов: {len(y_sampled)}")
print(f" На один период сигнала приходится {sr / freq:.1f} отсчётов")
# Сохраняем для прослушивания
sf.write(f'sine_{freq}hz_{sr}.wav', y_sampled, sr)
Этот код создаёт синусоидальный тон частотой 440 Гц — это нота «ля» первой октавы, стандартный камертон. Сначала мы создаём «идеальный» аналоговый сигнал, используя очень высокую частоту дискретизации — в десять раз выше CD. Затем мы имитируем процесс дискретизации: берём из этого идеального сигнала каждый n-ный отсчёт, чтобы получить сигнал с нужной частотой дискретизации. Мы пробуем четыре варианта: 8 000 Гц (телефонное качество), 11 025 Гц (четверть CD), 22 050 Гц (половина CD) и 44 100 Гц (полное CD-качество).
Запустите скрипт и прослушайте полученные файлы. Файл с частотой 44 100 Гц звучит как чистый, гладкий тон. Файл с 22 050 Гц — почти так же, разницу на слух уловить трудно. Файл с 11 025 Гц — звук заметно грубее, появляется лёгкое дребезжание. Файл с 8 000 Гц — тон всё ещё различим, но качество сильно страдает. Это потому, что на каждый период сигнала при частоте 8 000 Гц приходится всего около 18 отсчётов, а при 44 100 Гц — около 100. Чем больше отсчётов на период, тем точнее мы можем восстановить форму волны.
Теорема Найквиста и алиасинг
Теперь давайте проверим саму теорему Найквиста на практике. Теорема утверждает: чтобы точно оцифровать сигнал с частотой F, частота дискретизации должна быть строго больше 2F. Что будет, если попытаться оцифровать сигнал с частотой, которая выше половины частоты дискретизации? Возникнет алиасинг — ложные частоты, которых не было в исходном сигнале.
Представьте себе колесо автомобиля в кино. Вы наверняка замечали, что иногда колеса на экране крутятся в обратную сторону или очень медленно, хотя машина едет быстро. Это и есть алиасинг. Камера снимает с частотой 24 кадра в секунду. Если колесо делает чуть меньше 24 оборотов в секунду, каждый кадр захватывает спицы чуть раньше, чем они завершили полный оборот. В результате нам кажется, что колесо вращается назад. Частота вращения колеса выше половины частоты съёмки — и возникает ложное изображение.
В звуке то же самое. Если частота сигнала выше половины частоты дискретизации, после оцифровки она «отражается» и появляется на более низкой частоте. Эта частота-призрак и называется алиасом.
python
import numpy as np
import soundfile as sf
def demonstrate_aliasing(signal_freq, sample_rate, duration=1.0):
"""
Демонстрирует эффект алиасинга.
signal_freq - частота исходного сигнала
sample_rate - частота дискретизации
"""
nyquist = sample_rate / 2
print(f"Частота сигнала: {signal_freq} Гц")
print(f"Частота Найквиста: {nyquist} Гц")
if signal_freq <= nyquist:
print(" Частота сигнала <= частота Найквиста. Алиасинга нет.")
else:
# Вычисляем частоту алиаса
alias_freq = abs(signal_freq - sample_rate * round(signal_freq / sample_rate))
print(f" Частота сигнала > частота Найквиста! Возникает алиасинг.")
print(f" Ложная частота (алиас): {alias_freq:.1f} Гц")
# Генерируем сигнал на высокой частоте
sr_high = 441000
t = np.linspace(0, duration, int(sr_high * duration), endpoint=False)
y = np.sin(2 * np.pi * signal_freq * t)
# Дискретизируем
step = sr_high // sample_rate
y_sampled = y[::step]
# Сохраняем
sf.write(f'alias_{signal_freq}hz_at_{sample_rate}.wav', y_sampled, sample_rate)
print(f" Файл сохранён: alias_{signal_freq}hz_at_{sample_rate}.wav\n")
# Проверяем на нескольких примерах
demonstrate_aliasing(signal_freq=5000, sample_rate=44100) # должно быть чисто