18+
реклама
18+
Бургер менюБургер меню

Александр Иванов – Цифровая обработка сигналов на Python. От инженера к разработчику. (страница 4)

18

Для речи высокая частота дискретизации не нужна. Человеческий голос редко поднимается выше 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) # должно быть чисто