Полное руководство по извлечению аудиофункций во временной и частотной области с использованием Python

Содержание

  1. Введение
  2. Извлечение характеристик во временной области
    2.1 Основы обработки аудиосигнала: размер кадра и длина скачка
    2.2 Функция 1: Огибающая амплитуды
    2.3 Функция 2: Среднеквадратичная энергия
    2.4 Функция 3: Крест-фактор
    2,5 Функция 4: Скорость пересечения нуля
  3. Извлечение признаков в частотной области
    3.1 Характеристика 5: Отношение энергии полосы
    3.2 Характеристика 6: Спектральный центроид
    3.3 Характеристика 7: Спектральная полоса пропускания
    3.4 Характеристика 8: Спектральная неравномерность
  4. Заключение
  5. Рекомендации

Введение

Способность обрабатывать и анализировать разного рода данные для получения практических выводов является одним из самых важных навыков в век информации. Данные окружают нас повсюду: от книг, которые мы читаем, до фильмов, которые мы смотрим, от постов в Instagram, которые нам нравятся, до музыки, которую мы слушаем. В этой статье мы попытаемся понять основы обработки аудиосигнала:

  1. Как компьютер считывает звуковой сигнал
  2. Что такое функции во временной и частотной областях?
  3. Как извлекаются эти функции?
  4. Почему эти функции должны быть извлечены?

В частности, мы подробно рассмотрим следующие функции:

  • Характеристики во временной области: огибающая амплитуды, среднеквадратическая энергия, коэффициент амплитуды (и отношение пиковой мощности к средней), скорость пересечения нуля.
  • Характеристики частотной области: соотношение энергий полосы, спектральный центроид, спектральная ширина полосы (разброс), спектральная неравномерность.

Мы опишем теорию и напишем код Python с нуля, чтобы извлечь каждую из этих функций для аудиосигналов от 3 различных музыкальных инструментов: акустической гитары, медных духовых и ударной установки. Используемые образцы файлов аудиоданных можно скачать здесь: https://github.com/namanlab/Audio-Signal-Processing-Feature-Extraction.

Весь файл кода также доступен в указанном выше репозитории или доступен по этой ссылке: https://github.com/namanlab/Audio-Signal-Processing-Feature-Extraction/blob/main/Audio_Signal_Extraction.ipynb

Извлечение признаков во временной области

Начнем с того, что вспомним, что такое звук и как он воспринимается нами. Как некоторые из вас, возможно, помнят из школьных уроков, звук — это распространение вибраций в среде. Производство звука приводит в колебание окружающие молекулы воздуха, что проявляется в чередующихся областях сжатия (высокое давление) и разрежения (низкое давление). Эти сжатия и разрежения проходят через среду и достигают наших ушей, позволяя нам воспринимать звук таким, какой он есть. Таким образом, распространение звука связано с передачей этих изменений давления во времени. Представление звука во временной области включает захват и анализ этих изменений давления в разные интервалы времени путем дискретизации звуковой волны в дискретные моменты времени (обычно с использованием техники цифровой аудиозаписи). Каждый образец представляет собой уровень звукового давления в определенный момент. Построив эти образцы, мы получим форму волны, которая показывает, как уровень звукового давления изменяется во времени. Горизонтальная ось представляет время, а вертикальная ось представляет собой амплитуду или интенсивность звука, обычно масштабируемую для соответствия между -1 и 1, где положительные значения указывают на сжатие, а отрицательные значения указывают на разрежение. Это помогает нам дать визуальное представление о характеристиках звуковой волны, таких как ее амплитуда, частота и продолжительность.

Чтобы извлечь форму волны данного аудио с помощью Python, мы начинаем с загрузки необходимых пакетов:

import numpy as np
import matplotlib.pyplot as plt

import librosa
import librosa.display
import IPython.display as ipd
import scipy as spp

NumPy — популярный пакет Python для обработки и работы с массивами и матрицами. Он содержит широкий спектр инструментов от линейной алгебры до упрощения многих задач!

librosa — это пакет Python для обработки и анализа звука, который содержит несколько функций и инструментов, упрощающих использование различных аудиофункций. Как было сказано ранее, мы будем анализировать формы волны для 3 различных музыкальных инструментов: акустической гитары, медных духовых и барабанной установки. Вы можете загрузить аудиофайлы по ссылке, предоставленной ранее, и загрузить их в свой локальный репозиторий. Для прослушивания аудиофайлов мы используем IPython.display. Код приведен ниже:

# Listen to the audio files
# Ensure correct relative / absolute path to the sound files.

acoustic_guitar_path = "acoustic_guitar.wav"
ipd.Audio(acoustic_guitar_path)

brass_path = "brass.wav"
ipd.Audio(brass_path)

# Keep volume low!
drum_set_path = "drum_set.wav"
ipd.Audio(drum_set_path)

Затем мы загружаем музыкальные файлы в librosa с помощью функции librosa.load(). Эта функция позволяет нам анализировать аудиофайл и возвращать два объекта:

  1. y (массив NumPy): содержит значения амплитуды для разных временных интервалов. Попробуйте распечатать массив, чтобы убедиться в этом сами!
  2. sr (число › 0): частота дискретизации.

Частота дискретизации относится к количеству выборок, сделанных в единицу времени при преобразовании аналогового сигнала в его цифровое представление. Как обсуждалось выше, изменение давления в среде представляет собой аналоговый сигнал, форма волны которого непрерывно изменяется во времени. Теоретически для хранения непрерывных данных потребуется бесконечное количество места. Таким образом, для обработки и хранения этих аналоговых сигналов в цифровом виде их необходимо преобразовать в дискретное представление. Здесь вступает в действие сэмплирование для захвата снимков экрана звуковой волны через дискретные (равномерно расположенные) интервалы времени. Интервал между этими интервалами фиксируется обратным значением частоты дискретизации.

Частота дискретизации определяет, как часто из аналогового сигнала берутся выборки, и поэтому измеряется в выборках в секунду или герцах (Гц). Более высокая частота дискретизации означает, что каждую секунду выполняется больше выборок, что приводит к более точному представлению исходного аналогового сигнала, но требует больше ресурсов памяти. Напротив, более низкая частота дискретизации означает, что каждую секунду выполняется меньше выборок, что приводит к менее точному представлению исходного аналогового сигнала, но требует меньше ресурсов памяти.

Обычная частота дискретизации по умолчанию — 22050. Однако в зависимости от приложения/памяти пользователь может выбрать более низкую или более высокую частоту дискретизации, которая может быть указана аргументом sr в librosa.load( ). При выборе подходящей частоты дискретизации для аналого-цифрового преобразования может быть важно знать о теореме дискретизации Найквиста-Шеннона, которая гласит, что для точного захвата и восстановления аналогового сигнала частота дискретизации должна быть как минимум в два раза выше. компонент с самой высокой частотой, присутствующий в звуковом сигнале (называемый скоростью/частотой Найквиста).

Выполняя выборку на частоте выше, чем частота Найквиста, мы можем избежать явления, называемого алиасингом, которое может искажать исходный сигнал. Обсуждение алиасинга не имеет особого значения для целей этой статьи. Если вам интересно узнать об этом больше, вот отличный источник: https://thewolfsound.com/what-is-aliasing-what-causes-it-how-to-avoid-it/

Ниже приведен код для чтения аудиосигналов:

# Load music in librosa
sr = 22050
acoustic_guitar, sr = librosa.load(acoustic_guitar_path, sr = sr)
brass, sr = librosa.load(brass_path, sr = sr)
drum_set, sr = librosa.load(drum_set_path, sr = sr)

В приведенном выше примере частота дискретизации равна 22050 (что также является частотой по умолчанию). Выполнение приведенного выше кода возвращает 3 массива, каждый из которых хранит значения амплитуды через дискретные интервалы времени (заданные частотой дискретизации). Затем мы визуализируем формы волны для каждого из 3 звуковых образцов, используя librosa.display.waveshow(). Была добавлена ​​некоторая прозрачность (путем установки альфа = 0,5) для более четкой визуализации плотности амплитуды во времени.

def show_waveform(signal, name=""):
    # Create a new figure with a specific size
    plt.figure(figsize=(15, 7))
    # Display the waveform of the signal using librosa
    librosa.display.waveshow(signal, alpha=0.5)
    # Set the title of the plot
    plt.title("Waveform for " + name)
    # Show the plot
    plt.show()

show_waveform(acoustic_guitar, "Acoustic Guitar")
show_waveform(brass, "Brass")
show_waveform(drum_set, "Drum Set")

Потратьте некоторое время, чтобы просмотреть приведенные выше графики. Подумайте о закономерностях, которые вы видите. В волновой форме акустической гитары можно выделить периодический паттерн, характеризующийся регулярными колебаниями амплитуды, что отражает гармонически насыщенный характер гитарного звука. Колебания соответствуют колебаниям, производимым струнами, которые генерируют сложную форму волны, состоящую из нескольких гармоник, вносящих свой вклад в характерный тон и тембр гитары.

Точно так же духовая форма волны также демонстрирует периодический рисунок, что приводит к постоянной высоте тона и тембру. Духовые инструменты воспроизводят звук через жужжание губ музыканта в мундштук. Это жужжание генерирует форму волны с отчетливыми гармониками и регулярным рисунком изменений амплитуды.

Напротив, форма волны барабанной установки не имеет четкого периодического рисунка, поскольку барабаны производят звук за счет удара барабанной палочки или руки по пластику или другим перкуссионным поверхностям, создавая сложные и нерегулярные формы волны с различной амплитудой и продолжительностью. Отсутствие различимой периодической картины отражает ударный и нетональный характер звуков барабана.

Основы обработки аудиосигнала: размер кадра и длина скачка

Прежде чем обсуждать жизненно важные функции аудио во временной области, необходимо поговорить о двух жизненно важных параметрах извлечения функций: размере кадра и длине скачка. Обычно после цифровой обработки сигнала он разбивается на кадры (набор дискретных временных интервалов, которые могут перекрываться или не перекрываться). Длина кадра описывает размер этих кадров, а длина перехода инкапсулирует информацию о том, насколько кадры перекрываются. Но почему кадрирование важно?

Цель кадрирования состоит в том, чтобы зафиксировать изменение во времени различных характеристик сигнала. Обычные методы извлечения признаков дают однозначную сводку входного сигнала (например, среднее значение, минимум или максимум). Проблема прямого использования этих методов извлечения признаков заключается в том, что это полностью уничтожает любую информацию, связанную со временем. Например, если вас интересует вычисление средней амплитуды вашего сигнала, вы получите сводку по одному числу, скажем, x. Однако, естественно, есть интервалы, когда среднее значение меньше, и другие, когда среднее значение больше. Получение сводки с одним числом исключает любую информацию об изменении среднего значения во времени. Решение, в свою очередь, состоит в том, чтобы разделить сигналы на кадры, например, [0 мс, 10 мс), [10 мс, 20 мс), … Среднее равно впоследствии вычисляется для части сигнала в каждом из этих временных отрезков, и этот коллективный набор признаков дает окончательный извлеченный вектор признаков, сводку признаков, зависящую от времени, не так ли круто!

Теперь подробнее об этих двух параметрах:

  • Размер кадра. Описывает размер каждого кадра. Например, если размер кадра равен 1024, вы включаете 1024 выборки в каждый кадр и вычисляете необходимые функции для каждого из этих наборов из 1024 выборок. В общем, рекомендуется иметь размер кадра как степень 2. Обоснование этого не важно для целей этой статьи. Но если вам интересно, это потому, что быстрое преобразование Фурье (очень эффективный алгоритм для преобразования сигнала из временной области в частотную область) требует, чтобы кадры имели размер, равный степени 2. Мы будем говорить больше о преобразованиях Фурье в последующих разделах.
  • Длина перехода. Относится к количеству выборок, на которое кадр продвигается вперед на каждом шаге в последовательности данных, т. е. к количеству выборок, которые мы сдвигаем вправо перед созданием нового кадра. Может быть полезно думать о кадре как о скользящем окне, которое перемещается по сигналу шагами, определяемыми длиной скачка. На каждом шаге окно применяется к новому участку сигнала или последовательности, и в этом сегменте выполняется извлечение признаков. Таким образом, длина скачка определяет перекрытие между последовательными аудиокадрами. Длина скачка, равная размеру кадра, означает отсутствие перекрытия, поскольку каждый кадр начинается точно там же, где заканчивается предыдущий. Однако, чтобы смягчить влияние явления, называемого спектральной утечкой (которое происходит при преобразовании сигнала из его временной области в частотную), применяется оконная функция, приводящая к потере данных по краям каждого кадра (техническое объяснение выходит за рамки этой статьи, но если вам интересно, не стесняйтесь проверить эту ссылку: https://dspillustrations.com/pages/posts/misc/spectral-leakage-zero-padding-and-frequency-resolution .html»). Таким образом, часто выбираются промежуточные длины переходов, чтобы сохранить выборки краев, что приводит к различной степени перекрытия между кадрами.

В общем, меньшая длина скачка обеспечивает более высокое временное разрешение, позволяя нам фиксировать больше деталей и быстрых изменений в сигнале. Однако это также увеличивает требования к памяти. И наоборот, большая длина скачка снижает временное разрешение, но также помогает уменьшить пространственную сложность.

Примечание. Для более четкой визуализации размер кадра на приведенном выше изображении показан довольно большим. Для практических целей выбранный размер кадра намного меньше (возможно, несколько 1000 выборок, около 20–40 мс).

Прежде чем перейти к различным методам извлечения признаков во временной области, давайте проясним некоторые математические обозначения. В этой статье мы будем использовать следующие обозначения:

  • xᵢ: амплитуда i-го образца
  • K: размер кадра
  • H: длина перехода

Особенность 1: Огибающая амплитуды

Во-первых, давайте поговорим об огибающей амплитуды. Это одна из самых простых для вычисления (но весьма полезных) функций анализа во временной области. Огибающая амплитуды кадра аудиосигнала — это просто максимальное значение его амплитуды в этом кадре. Математически огибающая амплитуды (для неперекрывающихся кадров) k-го кадра определяется как:

В общем, для любого кадра k, содержащего отсчеты xⱼ₁ , xⱼ₂ , · · · , xⱼₖ, огибающая амплитуды равна:

Код Python для вычисления огибающей амплитуды данного сигнала приведен ниже:

FRAME_SIZE = 1024
HOP_LENGTH = 512

def amplitude_envelope(signal, frame_size=1024, hop_length=512):
    """
    Computes the Amplitude Envelope of a signal using a sliding window.

    Args:
        signal (array): The input signal.
        frame_size (int): The size of each frame in samples.
        hop_length (int): The number of samples between consecutive frames.

    Returns:
        np.array: An array of Amplitude Envelope values.
    """
    res = []
    for i in range(0, len(signal), hop_length):
        # Get a portion of the signal
        cur_portion = signal[i:i + frame_size]  
        # Compute the maximum value in the portion
        ae_val = max(cur_portion)  
        # Store the amplitude envelope value
        res.append(ae_val)  
    # Convert the result to a NumPy array
    return np.array(res)

def plot_amplitude_envelope(signal, name, frame_size=1024, hop_length=512):
    """
    Plots the waveform of a signal with the overlay of Amplitude Envelope values.

    Args:
        signal (array): The input signal.
        name (str): The name of the signal for the plot title.
        frame_size (int): The size of each frame in samples.
        hop_length (int): The number of samples between consecutive frames.
    """
    # Compute the amplitude envelope
    ae = amplitude_envelope(signal, frame_size, hop_length)
    # Generate the frame indices
    frames = range(0, len(ae))  
    # Convert frames to time
    time = librosa.frames_to_time(frames, hop_length=hop_length)  
    # Create a new figure with a specific size
    plt.figure(figsize=(15, 7))  
    # Display the waveform of the signal
    librosa.display.waveshow(signal, alpha=0.5)  
    # Plot the amplitude envelope over time
    plt.plot(time, ae, color="r") 
    # Set the title of the plot
    plt.title("Waveform for " + name + " (Amplitude Envelope)")  
    # Show the plot
    plt.show()  
    
plot_amplitude_envelope(acoustic_guitar, "Acoustic Guitar")
plot_amplitude_envelope(brass, "Brass")
plot_amplitude_envelope(drum_set, "Drum Set")

В приведенном выше коде мы определили функцию с именем amplitude_envelope, которая принимает массив входных сигналов (сгенерированный с помощью librosa.load()), размер кадра (K) и длина перехода (H) и возвращает массив размера, равного количеству кадров. k-е значение в массиве соответствует значению огибающей амплитуды для k-го кадра. Вычисление выполняется с использованием простого цикла for, который перебирает весь сигнал с шагами, определяемыми длиной скачка. Список (res) определяется для хранения этих значений и, наконец, преобразуется в массив NumPy перед возвратом. Определена другая функция, называемая plot_amplitude огибающей, которая принимает тот же набор входных данных (вместе с аргументом имени) и накладывает график огибающей амплитуды на исходный кадр. Для построения формы сигнала используется традиционная функция librosa.display.waveform(), как объяснялось в предыдущем разделе.

Чтобы построить огибающую амплитуды, нам нужны время и соответствующие значения огибающей амплитуды. Значения времени получаются с помощью очень полезной функции librosa.frames_to_times(), которая принимает два входных параметра: итерируемый, соответствующий количеству кадров (который определяется с помощью диапазона функция) и длина прыжка), чтобы сгенерировать среднее время для каждого кадра. Впоследствии matplotlib.pyplot используется для наложения красного графика. Описанный выше процесс будет последовательно использоваться для всех методов извлечения признаков во временной области.

На следующих рисунках показана вычисленная огибающая амплитуды для каждого из музыкальных инструментов. Они были добавлены в виде красной линии поверх исходной формы волны и имеют тенденцию приближаться к верхней границе формы волны. Огибающая амплитуды не только сохраняет периодическую картину, но также указывает на общую разницу в амплитудах звука, что отражается в более низкой интенсивности медных духовых по сравнению с акустической гитарой и барабанными установками.

Особенность 2: Среднеквадратичная энергия

Далее, давайте поговорим о среднеквадратичной энергии (RMSE), еще одной важной функции анализа во временной области. Среднеквадратичная энергия для кадра аудиосигнала получается путем извлечения квадратного корня из среднего квадрата всех значений амплитуды в кадре. Математически среднеквадратическая энергия (для непересекающихся кадров) k-го кадра определяется выражением:

В общем, для любого кадра k, содержащего выборки xⱼ₁ , xⱼ₂ , · · · , xⱼₖ, среднеквадратичное отклонение равно:

Энергия RMS обеспечивает представление общей интенсивности или силы звукового сигнала, принимая во внимание как положительные, так и отрицательные отклонения формы волны, обеспечивая более точное измерение мощности сигнала по сравнению с другими показателями, такими как пиковая амплитуда. Код Python для вычисления RMSE данного сигнала приведен ниже. Структура кода такая же, как и для генерации огибающей амплитуды. Единственное изменение заключается в функции, используемой для извлечения признака. Вместо максимального значения среднеквадратичная ошибка вычисляется путем взятия среднего квадрата значений в текущей части сигнала, за которым следует квадратный корень.

def RMS_energy(signal, frame_size=1024, hop_length=512):
    """
    Computes the RMS (Root Mean Square) energy of a signal using a sliding window.

    Args:
        signal (array): The input signal.
        frame_size (int): The size of each frame in samples.
        hop_length (int): The number of samples between consecutive frames.

    Returns:
        np.array: An array of RMS energy values.
    """
    res = []
    for i in range(0, len(signal), hop_length):
        # Extract a portion of the signal
        cur_portion = signal[i:i + frame_size]  
        # Compute the RMS energy for the portion
        rmse_val = np.sqrt(1 / len(cur_portion) * sum(i**2 for i in cur_portion))  
        res.append(rmse_val)
    # Convert the result to a NumPy array
    return np.array(res)

def plot_RMS_energy(signal, name, frame_size=1024, hop_length=512):
    """
    Plots the waveform of a signal with the overlay of RMS energy values.

    Args:
        signal (array): The input signal.
        name (str): The name of the signal for the plot title.
        frame_size (int): The size of each frame in samples.
        hop_length (int): The number of samples between consecutive frames.
    """
    # Compute the RMS Energy
    rmse = RMS_energy(signal, frame_size, hop_length)
    # Generate the frame indices
    frames = range(0, len(rmse))  
    # Convert frames to time
    time = librosa.frames_to_time(frames, hop_length=hop_length) 
    # Create a new figure with a specific size
    plt.figure(figsize=(15, 7))
    # Display the waveform as a spectrogram-like plot
    librosa.display.waveshow(signal, alpha=0.5)  
    # Plot the RMS energy values
    plt.plot(time, rmse, color="r") 
    # Set the title of the plot
    plt.title("Waveform for " + name + " (RMS Energy)")
    plt.show()

plot_RMS_energy(acoustic_guitar, "Acoustic Guitar")
plot_RMS_energy(brass, "Brass")
plot_RMS_energy(drum_set, "Drum Set")

На следующих рисунках показаны рассчитанные среднеквадратичные значения энергии для каждого из музыкальных инструментов. Они были добавлены в виде красной линии поверх исходной формы волны и имеют тенденцию приближаться к центру масс формы волны. Как и прежде, эта мера не только сохраняет периодичность, но и аппроксимирует общий уровень интенсивности звуковой волны.

Особенность 3: Крест-фактор

Теперь давайте поговорим о коэффициенте амплитуды, мера экстремальности пиков в форме волны. Пик-фактор для кадра аудиосигнала получается путем деления пиковой амплитуды (наибольшего абсолютного значения амплитуды) на среднеквадратичное значение энергии. Математически коэффициент амплитуды (для неперекрывающихся кадров) k-го кадра определяется как:

В общем, для любого кадра k, содержащего выборки xⱼ₁ , xⱼ₂ , · · · , xⱼₖ, коэффициент амплитуды равен:

Коэффициент амплитуды показывает отношение самого высокого пикового уровня к среднему уровню интенсивности сигнала. Код Python для вычисления коэффициента амплитуды данного сигнала приведен ниже. Структура аналогична приведенной выше, включая вычисление значения среднеквадратичной ошибки (знаменатель) и наивысшего пикового значения (числитель), которое затем используется для получения желаемой дроби (коэффициент амплитуды!).

def crest_factor(signal, frame_size=1024, hop_length=512):
    """
    Computes the crest factor of a signal using a sliding window.

    Args:
        signal (array): The input signal.
        frame_size (int): The size of each frame in samples.
        hop_length (int): The number of samples between consecutive frames.

    Returns:
        np.array: An array of crest factor values.
    """
    res = []
    for i in range(0, len(signal), hop_length):
        # Get a portion of the signal
        cur_portion = signal[i:i + frame_size]  
        # Compute the RMS energy for the portion
        rmse_val = np.sqrt(1 / len(cur_portion) * sum(i ** 2 for i in cur_portion))  
        # Compute the crest factor
        crest_val = max(np.abs(cur_portion)) / rmse_val  
        # Store the crest factor value
        res.append(crest_val)  
    # Convert the result to a NumPy array
    return np.array(res)  

def plot_crest_factor(signal, name, frame_size=1024, hop_length=512):
    """
    Plots the crest factor of a signal over time.

    Args:
        signal (array): The input signal.
        name (str): The name of the signal for the plot title.
        frame_size (int): The size of each frame in samples.
        hop_length (int): The number of samples between consecutive frames.
    """
    # Compute the crest factor
    crest = crest_factor(signal, frame_size, hop_length)  
    # Generate the frame indices
    frames = range(0, len(crest))  
    # Convert frames to time
    time = librosa.frames_to_time(frames, hop_length=hop_length)  
    # Create a new figure with a specific size
    plt.figure(figsize=(15, 7))  
    # Plot the crest factor over time
    plt.plot(time, crest, color="r")  
    # Set the title of the plot
    plt.title(name + " (Crest Factor)")  
    # Show the plot
    plt.show()  
    
plot_crest_factor(acoustic_guitar, "Acoustic Guitar")
plot_crest_factor(brass, "Brass")
plot_crest_factor(drum_set, "Drum Set")

На следующих рисунках показан вычисленный коэффициент амплитуды для каждого из музыкальных инструментов:

Более высокий коэффициент амплитуды, наблюдаемый для акустической гитары и медных духовых инструментов, указывает на большую разницу между пиковыми уровнями и средними уровнями, что предполагает более динамичный или пиковый сигнал с большими вариациями амплитуды. Более низкий коэффициент амплитуды, наблюдаемый для барабанной установки, предполагает более однородный или сжатый сигнал с меньшими колебаниями амплитуды. Коэффициент амплитуды особенно актуален в тех случаях, когда необходимо учитывать запас по запасу или доступный динамический диапазон системы. Например, высокий коэффициент амплитуды в музыкальной записи может потребовать тщательного рассмотрения, чтобы предотвратить искажения или клиппирование при воспроизведении на оборудовании с ограниченным запасом мощности.

Фактически, есть еще одна характеристика, называемая отношением пиковой мощности к средней (PAPR), тесно связанная с коэффициентом амплитуды. PAPR — это просто квадратное значение коэффициента амплитуды, обычно конвертируемое в коэффициент мощности в децибелах. В общем, для любого кадра k, содержащего отсчеты xⱼ₁ , xⱼ₂ , · · · , xⱼₖ, отношение пиковой мощности к средней мощности равно:

В качестве забавной задачи попробуйте изменить приведенный выше код, чтобы создать график PAPR для каждого из трех музыкальных инструментов и проанализировать свои выводы.

Особенность 4: Скорость пересечения нуля

Наконец, мы поговорим о скорости пересечения нуля (ZCR). Скорость пересечения нуля для кадра аудиосигнала — это просто количество пересечений сигнала через ноль (ось х/время). Математически ZCR (для неперекрывающихся кадров) k-го кадра определяется как:

Если последовательные значения имеют одинаковый знак, выражение внутри абсолютного значения отменяется, давая 0. Если они имеют противоположные знаки (указывая, что сигнал пересек ось времени), значения складываются, что дает 2 (после получения абсолютного значения) . Поскольку каждое пересечение нуля дает значение 2, мы умножаем результат на половину, чтобы получить требуемый счет. В общем, для любого фрейма k, содержащего отсчеты xⱼ₁ , xⱼ₂ , · · · , xⱼₖ, ZCR равен:

Обратите внимание, что в приведенном выше выражении скорость пересечения нуля вычисляется простым суммированием количества пересечений оси сигналом. Однако в соответствии с приложением можно также нормализовать значения (путем деления на длину кадра). Код Python для вычисления коэффициента амплитуды данного сигнала приведен ниже. Структура следует из вышеизложенного с определением другой функции, называемой изменением знака числа, которая определяет, сколько раз знак меняется в данном сигнале.

def ZCR(signal, frame_size=1024, hop_length=512):
    """
    Computes the Zero Crossing Rate (ZCR) of a signal using a sliding window.

    Args:
        signal (array): The input signal.
        frame_size (int): The size of each frame in samples.
        hop_length (int): The number of samples between consecutive frames.

    Returns:
        np.array: An array of ZCR values.
    """
    res = []
    for i in range(0, len(signal), hop_length):
        # Get a portion of the signal
        cur_portion = signal[i:i + frame_size]  
         # Compute the number of sign changes in the portion
        zcr_val = num_sign_changes(cur_portion) 
        # Store the ZCR value
        res.append(zcr_val)  
    # Convert the result to a NumPy array
    return np.array(res)  

def num_sign_changes(signal):
    """
    Computes the number of sign changes in a signal.

    Args:
        signal (array): The input signal.

    Returns:
        int: The number of sign changes.
    """
    res = 0
    for i in range(0, len(signal) - 1):
        # Check if there is a sign change between consecutive samples
        if (signal[i] * signal[i + 1] < 0):  
            res += 1
    return res

def plot_ZCR(signal, name, frame_size=1024, hop_length=512):
    """
    Plots the Zero Crossing Rate (ZCR) of a signal over time.

    Args:
        signal (array): The input signal.
        name (str): The name of the signal for the plot title.
        frame_size (int): The size of each frame in samples.
        hop_length (int): The number of samples between consecutive frames.
    """
    # Compute the ZCR
    zcr = ZCR(signal, frame_size, hop_length)  
    # Generate the frame indices
    frames = range(0, len(zcr)) 
    # Convert frames to time
    time = librosa.frames_to_time(frames, hop_length=hop_length)  
     # Create a new figure with a specific size
    plt.figure(figsize=(15, 7)) 
    # Plot the ZCR over time
    plt.plot(time, zcr, color="r")  
    # Set the title of the plot
    plt.title(name + " (Zero Crossing Rate)")  
    # Show the plot
    plt.show()  
    
plot_ZCR(acoustic_guitar, "Acoustic Guitar")
plot_ZCR(brass, "Brass")
plot_ZCR(drum_set, "Drum Set")

На следующих рисунках показана вычисленная частота пересечения нуля для каждого из музыкальных инструментов.

Более высокая скорость пересечения нуля показывает, что сигнал часто меняет свое направление, что предполагает наличие высокочастотных компонентов или более динамичной формы волны. И наоборот, более низкая скорость пересечения нуля указывает на относительно более гладкую или постоянную форму волны.

Скорость пересечения нуля особенно полезна в приложениях для анализа речи и музыки из-за ее способности давать представление о таких свойствах, как тембр и ритмические паттерны. Например, при анализе речи частота пересечения нуля помогает различать вокализованные и невокализованные звуки, поскольку вокализованные звуки, как правило, имеют более высокую скорость пересечения нуля из-за вибрации голосовых связок. Важно отметить, что, хотя скорость пересечения нуля является простой и эффективной в вычислительном отношении функцией, она может не охватывать все аспекты сложности сигнала (как вы можете видеть на приведенных выше рисунках, периодичность полностью потеряна). Поэтому его часто используют в сочетании с другими функциями для более полного анализа аудиосигналов.

Извлечение характеристик частотной области

Частотная область предлагает альтернативное представление звуковой волны. В отличие от временной области, где сигнал представлен как функция времени, в частотной области сигнал разлагается на составляющие его частоты, раскрывая информацию об амплитуде и фазе, связанную с каждой частотой, т. е. сигнал представляется как функция времени. частота. Вместо того, чтобы смотреть на амплитуду сигнала в различные моменты времени, мы изучаем амплитуды различных частотных составляющих, составляющих сигнал. Каждая частотная составляющая представляет собой синусоидальную волну определенной частоты, и, комбинируя эти составляющие, мы можем реконструировать исходный сигнал во временной области.

Наиболее распространенным математическим инструментом, используемым для преобразования сигнала из временной области в частотную, является преобразование Фурье. Преобразование Фурье принимает сигнал в качестве входного сигнала и разлагает его на сумму синусоидальных и косинусоидальных волн различной частоты, имеющих собственную амплитуду и фазу. Полученное представление и составляет частотный спектр. Математически преобразование Фурье непрерывного сигнала во временной области g(t) определяется следующим образом:

где i = √−1 — мнимое число. Да, преобразование Фурье дает сложный результат с фазой и величиной, соответствующими фазе и величине составляющей синусоиды! Однако для большинства приложений мы заботимся только о величине преобразования и просто игнорируем соответствующую фазу. Поскольку звук, обработанный цифровым способом, является дискретным, мы можем определить аналогичное дискретное преобразование Фурье (ДПФ):

где T – длительность одной выборки. С точки зрения частоты дискретизации:

Поскольку частотные представления также непрерывны, мы оцениваем преобразование Фурье на дискретизированных элементах разрешения по частоте, чтобы получить дискретное представление звуковой волны в частотной области. Это называется кратковременным преобразованием Фурье. Математически,

Не беспокойтесь! Давайте внимательно его рассмотрим. Hat-h(k) — это функция, которая отображает целое число k ∈ {0, 1, · · · , N − 1} в величину частоты k · Sᵣ/N. Обратите внимание, что мы рассматриваем только дискретные частотные элементы, которые являются целыми кратными Sᵣ/N, где N — количество выборок в сигнале. Если вы все еще не знаете, как это работает, вот отличное объяснение преобразований Фурье: https://www.youtube.com/watch?v=spUNpyF58BY&t=393s

Преобразование Фурье — одно из самых красивых математических нововведений, поэтому о нем стоит знать, хотя обсуждение не имеет прямого отношения к цели этой статьи. В Python вы можете легко получить краткосрочное преобразование Фурье, используя librosa.stft().

Примечание. Для больших аудиоданных существует более эффективный способ вычисления преобразования Фурье, называемый быстрым преобразованием Фурье (БПФ). Не стесняйтесь проверить его, если вам интересно!

Как и раньше, нас не просто интересует, какие частоты преобладают: мы также хотим показать, когда эти частоты преобладают. Итак, мы ищем одновременное частотно-временное представление, показывающее, какие частоты преобладают в какой момент времени. Здесь вступает в действие кадрирование: мы разбиваем сигнал на временные кадры и получаем величину результирующего преобразования Фурье в каждом кадре. Это дает нам матрицу значений, где количество строк определяется количеством элементов разрешения по частоте (Φ, обычно равным K/2 + 1, где K — размер кадра), а количество столбцов определяется количеством количество кадров. Поскольку преобразование Фурье дает комплексные выходные данные, сгенерированная матрица является комплексной. В Python параметры размера кадра и длины перехода можно легко указать в качестве аргументов, а результирующую матрицу можно просто вычислить с помощью librosa.stft(signal, n fft=размер кадра, длина перехода=длина перехода) . Поскольку нас интересует только величина, мы можем использовать numpy.abs() для преобразования комплексной матрицы в вещественную. Полученную матрицу довольно удобно построить, чтобы иметь наглядное представление сигнала, которое дает ценную информацию о частотном содержании и временных характеристиках данного звука. Так называемое представление называется спектрограммой.

Спектрограммы получаются путем построения временных рамок по оси x и частотных интервалов по оси y. Затем цвета используются для обозначения интенсивности или величины частоты для данного периода времени. Обычно ось частот преобразуется в логарифмическую шкалу (поскольку известно, что люди лучше воспринимают их при логарифмическом преобразовании), а величина выражается в децибелах.

Код Python для создания спектрограммы показан ниже:

FRAME_SIZE = 1024
HOP_LENGTH = 512

def plot_spectrogram(signal, sample_rate, frame_size=1024, hop_length=512):
    """
    Plots the spectrogram of an audio signal.

    Args:
        signal (array-like): The input audio signal.
        sample_rate (int): The sample rate of the audio signal.
        frame_size (int): The size of each frame in samples.
        hop_length (int): The number of samples between consecutive frames.
    """
    # Compute the STFT
    spectrogram = librosa.stft(signal, n_fft=frame_size, hop_length=hop_length)  
    # Convert the STFT to dB scale
    spectrogram_db = librosa.amplitude_to_db(np.abs(spectrogram))  
    # Create a new figure with a specific size
    plt.figure(figsize=(15, 7))  
    # Display the spectrogram
    librosa.display.specshow(spectrogram_db, sr=sample_rate, hop_length=hop_length, x_axis='time', y_axis='log') 
    # Add a colorbar to show the magnitude scale
    plt.colorbar(format='%+2.0f dB') 
    # Set the title of the plot
    plt.title('Spectrogram')  
    # Set the label for the x-axis
    plt.xlabel('Time') 
    # Set the label for the y-axis
    plt.ylabel('Frequency (Hz)')  
    # Adjust the layout of the plot
    plt.tight_layout()  
    # Show the plot
    plt.show()  
    
plot_spectrogram(acoustic_guitar, sr)
plot_spectrogram(brass, sr)
plot_spectrogram(drum_set, sr)

В приведенном выше коде мы определили функцию, называемую графиком спектрограммы, которая принимает 4 аргумента: массив входных сигналов, частоту дискретизации, размер кадра и длину скачка. Сначала librosa.stft() используется для получения матрицы спектрограммы. Затем np.abs() используется для извлечения амплитуды с последующим преобразованием значений амплитуды в децибелы с помощью функции librosa.amplitude_to_db(). Наконец, функция librosa.display.specshow() используется для построения спектрограммы. Эта функция принимает преобразованную матрицу спектрограммы, частоту дискретизации, длину скачка и спецификации для осей x и y. Логарифмически преобразованная ось Y может быть указана с использованием аргумента ось Y = ‘log’. Необязательную цветовую полосу можно добавить с помощью plt.colorbar(). Результирующие спектрограммы для трех музыкальных инструментов показаны ниже:

Спектрограммы предлагают уникальный способ визуализации соотношения времени и частоты. Временная область дает нам точное представление о том, как сигнал развивается во времени, а частотная область позволяет нам увидеть распределение энергии на разных частотах. Это позволяет нам идентифицировать не только наличие определенных частот, но также помогает понять их продолжительность и временные вариации. Спектрограммы являются одним из наиболее полезных способов представления звука и часто используются в приложениях машинного обучения звуковых сигналов (например, передача спектрограммы звуковой волны в глубокую сверточную нейронную сеть для прогнозирования).

Прежде чем перейти к различным методам извлечения признаков в частотной области, давайте проясним некоторые математические обозначения. В следующих разделах мы будем использовать следующие обозначения:

  • mₖ(i): амплитуда i-й частоты k-го кадра.
  • K: Размер кадра
  • H: длина прыжка
  • Φ: Количество элементов разрешения по частоте (= K/2 + 1)

Особенность 5: Коэффициент энергии полосы

Во-первых, давайте поговорим об отношении энергии полосы. Отношение энергии полосы - это метрика, используемая для количественной оценки отношения энергии более низких частот к энергии более высоких частот в заданном временном интервале. Математически для любого кадра k отношение энергии полосы равно:

где σբ обозначает разделенный частотный бин: параметр, позволяющий отличать более низкие частоты от более высоких частот. При расчете отношения энергии полосы все частоты, имеющие значение меньше, чем частота, соответствующая σբ (называемая разделенной частотой), рассматриваются как более низкие частоты. Сумма квадратов энергий или этих частот определяет числитель. Точно так же все частоты, имеющие значение выше частоты разделения, рассматриваются как более высокие частоты, и сумма квадратов энергий или этих частот определяет знаменатель. Код Python для вычисления отношения энергии полосы сигнала показан ниже:

def find_split_freq_bin(spec, split_freq, sample_rate, frame_size=1024, hop_length=512):
    """
    Calculate the bin index corresponding to a given split frequency.

    Args:
        spec (array): The spectrogram.
        split_freq (float): The split frequency in Hz.
        sample_rate (int): The sample rate of the audio.
        frame_size (int, optional): The size of each frame in samples. Default is 1024.
        hop_length (int, optional): The number of samples between consecutive frames. Default is 512.

    Returns:
        int: The bin index corresponding to the split frequency.
    """
    # Calculate the range of frequencies
    range_of_freq = sample_rate / 2
    # Calculate the change in frequency per bin
    change_per_bin = range_of_freq / spec.shape[0]
    # Calculate the bin corresponding to the split frequency
    split_freq_bin = split_freq / change_per_bin
    return int(np.floor(split_freq_bin))

def band_energy_ratio(signal, split_freq, sample_rate, frame_size=1024, hop_length=512):
    """
    Compute the band energy ratio (BER) of a signal.

    Args:
        signal (array): The input signal.
        split_freq (float): The split frequency in Hz.
        sample_rate (int): The sample rate of the audio.
        frame_size (int, optional): The size of each frame in samples. Default is 1024.
        hop_length (int, optional): The number of samples between consecutive frames. Default is 512.

    Returns:
        ndarray: The band energy ratios for each frame of the signal.
    """
    # Compute the spectrogram of the signal
    spec = librosa.stft(signal, n_fft=frame_size, hop_length=hop_length)
    # Find the bin corresponding to the split frequency
    split_freq_bin = find_split_freq_bin(spec, split_freq, sample_rate, frame_size, hop_length)
    # Extract the magnitude and transpose it
    modified_spec = np.abs(spec).T
    res = []
    for sub_arr in modified_spec:
        # Compute the energy in the low-frequency range
        low_freq_density = sum(i ** 2 for i in sub_arr[:split_freq_bin])
        # Compute the energy in the high-frequency range
        high_freq_density = sum(i ** 2 for i in sub_arr[split_freq_bin:])
        # Compute the band energy ratio
        ber_val = low_freq_density / high_freq_density
        res.append(ber_val)
    return np.array(res)

def plot_band_energy_ratio(signal, split_freq, sample_rate, name, frame_size=1024, hop_length=512):
    """
    Plot the band energy ratio (BER) of a signal over time.

    Args:
        signal (ndarray): The input signal.
        split_freq (float): The split frequency in Hz.
        sample_rate (int): The sample rate of the audio.
        name (str): The name of the signal for the plot title.
        frame_size (int, optional): The size of each frame in samples. Default is 1024.
        hop_length (int, optional): The number of samples between consecutive frames. Default is 512.
    """
    # Compute the band energy ratio (BER)
    ber = band_energy_ratio(signal, split_freq, sample_rate, frame_size, hop_length)
    # Generate the frame indices
    frames = range(0, len(ber))
    # Convert frames to time
    time = librosa.frames_to_time(frames, hop_length=hop_length)
    # Create a new figure with a specific size
    plt.figure(figsize=(15, 7))
    # Plot the BER over time
    plt.plot(time, ber)
    # Set the title of the plot
    plt.title(name + " (Band Energy Ratio)")
    # Show the plot
    plt.show()
    
plot_band_energy_ratio(acoustic_guitar, 2048, sr, "Acoustic Guitar")
plot_band_energy_ratio(brass, 2048, sr, "Brass")
plot_band_energy_ratio(drum_set, 2048, sr, "Drum Set")

Структура приведенного выше кода очень похожа на структуру извлечения во временной области. Первым шагом является определение функции find split_freq_bin(), которая берет спектрограмму, значение частоты разделения и частоту дискретизации для определения бина частоты разделения (σբ ), соответствующего разделению. частота. Процесс довольно прост. Он включает в себя определение диапазона частот (который, как объяснялось ранее, является частотой Найквиста, Sᵣ/2). Количество бинов по частоте определяется количеством строк спектрограмм, которые извлекаются как spec.shape[0]. Разделение общего диапазона частот на количество элементов разрешения по частоте позволяет нам вычислить изменение частоты на элемент разрешения, которое можно разделить на заданную частоту разделения для определения элемента разрешения разделения частоты.

Затем мы используем эту функцию для расчета вектора отношения энергий полосы. Функция band_energy_ratio() принимает входной сигнал, частоту разделения, частоту дискретизации, размер кадра и длину скачка. Во-первых, он использует librosa.stft() для извлечения спектрограммы, а затем вычисляет частотный бин разделения. Затем величина спектрограммы вычисляется с помощью np.abs() с последующим транспонированием для облегчения итерации по каждому кадру. Во время итерации отношение энергии полосы для каждого кадра вычисляется с использованием определенной формулы и найденного бина разделения частот. Значения сохраняются в списке res, который в конечном итоге возвращается в виде массива NumPy. Наконец, значения наносятся на график с помощью функции plot_band_energy_ratio().

Графики соотношения энергий полос для трех музыкальных инструментов показаны ниже. Для этих графиков частота разделения выбрана равной 2048 Гц, т. е. частоты ниже 2048 Гц считаются частотами с более низкой энергией, а частоты выше считаются частотами с более высокой энергией.

Высокий коэффициент энергии полосы (для латуни) указывает на большее присутствие низкочастотных компонентов по сравнению с более высокочастотными компонентами. Таким образом, мы наблюдаем, что духовые инструменты производят значительное количество энергии в более низких частотных диапазонах по сравнению с более высокими частотными диапазонами. Акустическая гитара имеет более низкий BER по сравнению с духовыми инструментами, что указывает на относительно меньший вклад энергии в низкочастотных диапазонах по сравнению с высокочастотными диапазонами. В целом акустические гитары, как правило, имеют более сбалансированное распределение энергии по частотному спектру с относительно меньшим акцентом на более низкие частоты по сравнению с другими инструментами. Наконец, ударная установка имеет самый низкий BER среди всех трех инструментов, что предполагает сравнительно меньший вклад энергии в низкочастотных диапазонах по сравнению с другими инструментами.

Особенность 6: Спектральный центроид

Далее мы поговорим о спектральном центроиде, показателе, который количественно определяет информацию о центре масс или средней частоте спектра сигнала в заданном временном интервале. Математически для любого кадра k спектральный центр тяжести равен:

Думайте об этом как о взвешенной сумме индексов частотного бина, где вес определяется вкладом энергии бина в заданный период времени. Нормализация также выполняется путем деления взвешенной суммы на сумму всех весов, чтобы облегчить единообразное сравнение различных сигналов. Код Python для вычисления спектрального центроида сигнала показан ниже:

def spectral_centroid(signal, sample_rate, frame_size=1024, hop_length=512):
    """
    Compute the Spectral Centroid of a signal.

    Args:
        signal (array): The input signal.
        sample_rate (int): The sample rate of the audio.
        frame_size (int, optional): The size of each frame in samples. Default is 1024.
        hop_length (int, optional): The number of samples between consecutive frames. Default is 512.

    Returns:
        ndarray: The spectral centroids for each frame of the signal.
    """
    # Compute the spectrogram of the signal
    spec = librosa.stft(signal, n_fft=frame_size, hop_length=hop_length)
    # Extract the magnitude and transpose it
    modified_spec = np.abs(spec).T
    res = []
    for sub_arr in modified_spec:
        # Compute the spectral centroid
        sc_val = sc(sub_arr)
        # Store the value of spectral centroid for current frame
        res.append(sc_val)
    return np.array(res)

def sc(arr):
    """
    Computes the spectral centroid in a signal.

    Args:
        arr (array): Frequency domain array for current frame.

    Returns:
        float: The spectral centroid value for current frame.
    """
    res = 0
    for i in range(0, len(arr)):
        # Compute weighted sum
        res += i*arr[i]
    return res/sum(arr)

def bin_to_freq(spec, bin_val, sample_rate, frame_size=1024, hop_length=512):
    """
    Calculate the frequency corresponding to a given bin value

    Args:
        spec (array): The spectrogram.
        bin_val (): The bin value.
        sample_rate (int): The sample rate of the audio.
        frame_size (int, optional): The size of each frame in samples. Default is 1024.
        hop_length (int, optional): The number of samples between consecutive frames. Default is 512.

    Returns:
        int: The bin index corresponding to the split frequency.
    """
    # Calculate the range of frequencies
    range_of_freq = sample_rate / 2
    # Calculate the change in frequency per bin
    change_per_bin = range_of_freq / spec.shape[0]
    # Calculate the frequency corresponding to the bin
    split_freq = bin_val*change_per_bin
    return split_freq

def plot_spectral_centroid(signal, sample_rate, name, frame_size=1024, hop_length=512, col = "black"):
    """
    Plot the spectral centroid of a signal over time.

    Args:
        signal (ndarray): The input signal.
        sample_rate (int): The sample rate of the audio.
        name (str): The name of the signal for the plot title.
        frame_size (int, optional): The size of each frame in samples. Default is 1024.
        hop_length (int, optional): The number of samples between consecutive frames. Default is 512.
    """
    # Compute the STFT
    spectrogram = librosa.stft(signal, n_fft=frame_size, hop_length=hop_length)  
    # Convert the STFT to dB scale
    spectrogram_db = librosa.amplitude_to_db(np.abs(spectrogram)) 
    # Compute the Spectral Centroid
    sc_arr = spectral_centroid(signal, sample_rate, frame_size, hop_length)
    # Compute corresponding frequencies:
    sc_freq_arr = bin_to_freq(spectrogram_db, sc_arr, sample_rate, frame_size, hop_length)
    # Generate the frame indices
    frames = range(0, len(sc_arr))
    # Convert frames to time
    time = librosa.frames_to_time(frames, hop_length=hop_length)
    # Create a new figure with a specific size
    plt.figure(figsize=(15, 7))
    # Display the Spectrogram
    librosa.display.specshow(spectrogram_db, sr=sample_rate, hop_length=hop_length, x_axis='time', y_axis='log') 
    # Add a colorbar to show the magnitude scale
    plt.colorbar(format='%+2.0f dB')  
    # Plot the Spectral Centroid over time
    plt.plot(time, sc_freq_arr, color=col)
    # Set the title of the plot
    plt.title(name + " (Spectral Centroid)")
    # Show the plot
    plt.show()
    
plot_spectral_centroid(acoustic_guitar, sr, "Acoustic Guitar")
plot_spectral_centroid(brass, sr, "Brass", col = "white")
plot_spectral_centroid(drum_set, sr, "Drum Set")

В приведенном выше коде функция спектрального центроида определена для создания массива спектральных центроидов для всех временных рамок. Затем определяется функция sc() для вычисления спектрального центроида одного кадра с помощью простого итеративного процесса, который умножает значения индекса на величину с последующей нормализацией для получения среднего частотного бина. Перед нанесением на график значений спектрального центроида, возвращаемых функцией spectral_centroid(), в качестве вспомогательной функции для построения графика определяется дополнительная функция, называемая bin to freq. Эта функция преобразует средние значения бина в соответствующие значения частоты, которые можно нанести на исходную спектрограмму, чтобы иметь последовательное представление об изменении спектрального центроида во времени. Выходные графики (с наложением изменения центроида на спектрограмму) показаны ниже:

Спектральный центроид вполне аналогичен метрике RMSE для анализа во временной области и обычно используется в качестве дескриптора тембра и яркости звука. Звуки с более высокими спектральными центроидами, как правило, имеют более яркое или более ориентированное на высокие частоты качество, в то время как более низкие значения центроида связаны с более темным или ориентированным на бас характером. Спектральный центроид — одна из наиболее важных функций для машинного обучения звука, часто используемая в приложениях, связанных с классификацией аудио/музыкальных жанров.

Функция 7: Спектральная полоса пропускания

Теперь мы поговорим о спектральной ширине/разбросе, показателе, который количественно определяет информацию о разбросе энергий по составляющим частотам спектра сигнала в заданный период времени. Подумайте об этом так: если спектральный центроид является средним/средним значением, спектральная полоса пропускания является мерой его разброса/дисперсии относительно центроида. Математически для любого кадра k спектральная ширина полосы равна:

где SCₖ обозначает спектральный центр тяжести k-го кадра. Как и прежде, нормализация выполняется путем деления взвешенной суммы на сумму всех весов, чтобы облегчить единообразное сравнение различных сигналов. Код Python для вычисления спектральной ширины полосы сигнала показан ниже:

def spectral_bandwidth(signal, sample_rate, frame_size=1024, hop_length=512):
    """
    Compute the Spectral Bandwidth of a signal.

    Args:
        signal (array): The input signal.
        sample_rate (int): The sample rate of the audio.
        frame_size (int, optional): The size of each frame in samples. Default is 1024.
        hop_length (int, optional): The number of samples between consecutive frames. Default is 512.

    Returns:
        ndarray: The spectral bandwidths for each frame of the signal.
    """
    # Compute the spectrogram of the signal
    spec = librosa.stft(signal, n_fft=frame_size, hop_length=hop_length)
    # Extract the magnitude and transpose it
    modified_spec = np.abs(spec).T
    res = []
    for sub_arr in modified_spec:
        # Compute the spectral bandwidth
        sb_val = sb(sub_arr)
        # Store the value of spectral bandwidth for current frame
        res.append(sb_val)
    return np.array(res)

def sb(arr):
    """
    Computes the spectral bandwidth in a signal.

    Args:
        arr (array): Frequency domain array for current frame.

    Returns:
        float: The spectral bandwidth value for current frame.
    """
    res = 0
    sc_val = sc(arr)
    for i in range(0, len(arr)):
        # Compute weighted sum
        res += (abs(i - sc_val))*arr[i]
    return res/sum(arr)

def plot_spectral_bandwidth(signal, sample_rate, name, frame_size=1024, hop_length=512):
    """
    Plot the spectral bandwidth of a signal over time.

    Args:
        signal (ndarray): The input signal.
        sample_rate (int): The sample rate of the audio.
        name (str): The name of the signal for the plot title.
        frame_size (int, optional): The size of each frame in samples. Default is 1024.
        hop_length (int, optional): The number of samples between consecutive frames. Default is 512.
    """
    # Compute the Spectral bandwidth
    sb_arr = spectral_bandwidth(signal, sample_rate, frame_size, hop_length)
    # Generate the frame indices
    frames = range(0, len(sb_arr))
    # Convert frames to time
    time = librosa.frames_to_time(frames, hop_length=hop_length)
    # Create a new figure with a specific size
    plt.figure(figsize=(15, 7))
    # Plot the Spectral Bandwidth over time
    plt.plot(time, sb_arr)
    # Set the title of the plot
    plt.title(name + " (Spectral Bandwidth)")
    # Show the plot
    plt.show()
    
plot_spectral_bandwidth(acoustic_guitar, sr, "Acoustic Guitar")
plot_spectral_bandwidth(brass, sr, "Brass")
plot_spectral_bandwidth(drum_set, sr, "Drum Set")

Как и прежде, в приведенном выше коде функция спектральной полосы пропускания определяется для создания массива спектрального расширения для всех временных кадров с помощью вспомогательной функции sb, которая итеративно вычисляет ширину полосы одного кадра. Наконец, функция спектральной полосы пропускания используется для построения графика этих значений ширины полосы. Выходные графики показаны ниже:

Спектральная полоса пропускания может использоваться в различных задачах анализа/классификации звука благодаря ее способности предоставлять информацию о разбросе или ширине частот, присутствующих в сигнале. Более широкий спектральный диапазон (как видно для медных духовых и барабанных установок) указывает на более широкий диапазон частот, что предполагает более разнообразный или сложный сигнал. С другой стороны, меньшая полоса пропускания предполагает более узкий диапазон частот, что указывает на более сфокусированный или тонально чистый сигнал.

Особенность 8: Спектральная плоскостность

Наконец, мы поговорим о спектральной неравномерности (также известной как энтропия Вайнера), мере, которая информирует о неравномерности или однородности спектра мощности аудиосигнала. Он помогает нам узнать, насколько звуковой сигнал близок к чистому тону (в отличие от шумоподобного), и поэтому также называется коэффициентом тональности. Для любого кадра k спектральная плоскостность представляет собой отношение его среднего геометрического к среднему арифметическому. Математически,

Код Python для вычисления спектральной неравномерности сигнала показан ниже:

def spectral_flatness(signal, sample_rate, frame_size=1024, hop_length=512):
    """
    Compute the Spectral Flatness of a signal.

    Args:
        signal (array): The input signal.
        sample_rate (int): The sample rate of the audio.
        frame_size (int, optional): The size of each frame in samples. Default is 1024.
        hop_length (int, optional): The number of samples between consecutive frames. Default is 512.

    Returns:
        ndarray: The spectral flatness for each frame of the signal.
    """
    # Compute the spectrogram of the signal
    spec = librosa.stft(signal, n_fft=frame_size, hop_length=hop_length)
    # Extract the magnitude and transpose it
    modified_spec = np.abs(spec).T
    res = []
    for sub_arr in modified_spec:
        # Compute the geometric mean
        geom_mean = np.exp(np.log(sub_arr).mean())
        # Compute the arithmetic mean
        ar_mean = np.mean(sub_arr)
        # Compute the spectral flatness
        sl_val = geom_mean/ar_mean
        # Store the value of spectral flatness for current frame
        res.append(sl_val)
    return np.array(res)

def plot_spectral_flatness(signal, sample_rate, name, frame_size=1024, hop_length=512):
    """
    Plot the spectral flatness of a signal over time.

    Args:
        signal (ndarray): The input signal.
        sample_rate (int): The sample rate of the audio.
        name (str): The name of the signal for the plot title.
        frame_size (int, optional): The size of each frame in samples. Default is 1024.
        hop_length (int, optional): The number of samples between consecutive frames. Default is 512.
    """
    # Compute the Spectral bandwidth
    sl_arr = spectral_flatness(signal, sample_rate, frame_size, hop_length)
    # Generate the frame indices
    frames = range(0, len(sl_arr))
    # Convert frames to time
    time = librosa.frames_to_time(frames, hop_length=hop_length)
    # Create a new figure with a specific size
    plt.figure(figsize=(15, 7))
    # Plot the Spectral Flatness over time
    plt.plot(time, sl_arr)
    # Set the title of the plot
    plt.title(name + " (Spectral Flatness)")
    # Show the plot
    plt.show()
    
plot_spectral_flatness(acoustic_guitar, sr, "Acoustic Guitar")
plot_spectral_flatness(brass, sr, "Brass")
plot_spectral_flatness(drum_set, sr, "Drum Set")

Структура приведенного выше кода такая же, как и для других методов извлечения в частотной области. Единственное отличие заключается в функции извлечения признаков внутри цикла for, которая вычисляет среднее арифметическое и геометрическое с использованием функций NumPy и вычисляет их отношение для получения значений спектральной неравномерности для каждого временного интервала. Выходные графики показаны ниже:

Высокое значение спектральной неравномерности (ближе к 1) указывает на более равномерное или сбалансированное распределение энергии по разным частотам в сигнале. Это постоянно наблюдается для барабанной установки, предполагая, что звук более «шумовой» или широкополосный, без заметных пиков или акцента на определенных частотах (как было замечено ранее из-за отсутствия периодичности).

С другой стороны, низкое значение спектральной неравномерности (особенно для акустической гитары и отчасти для медных духовых) подразумевает более неравномерный спектр мощности, когда энергия концентрируется вокруг нескольких определенных частот. Это показывает наличие в звуке тональных или гармонических компонентов (что отражается в их периодической структуре во временной области). В целом, музыка с разной высотой тона/частотами обычно имеет более низкие значения спектральной неравномерности, в то время как более шумные (и нетональные) звуки демонстрируют более высокие значения спектральной неравномерности.

Заключение

В этой статье мы подробно рассмотрели различные стратегии и методы извлечения признаков, которые составляют неотъемлемую часть обработки аудиосигналов в музыкальной инженерии. Мы начали с изучения основ производства и распространения звука, которые можно эффективно преобразовать в изменения давления во времени, что привело к его представлению во временной области. Мы обсудили цифровое представление звука и его жизненно важные параметры, включая частоту дискретизации, размер кадра и длину скачка. Характеристики временной области, такие как огибающая амплитуды, среднеквадратическая энергия, коэффициент амплитуды, отношение пиковой мощности к мощности и скорость пересечения нуля, обсуждались теоретически и оценивались с помощью вычислений на трех музыкальных инструментах: акустической гитаре, медных духовых и барабанных установках. Впоследствии представление звука в частотной области было представлено и проанализировано посредством различных теоретических дискуссий о преобразовании Фурье и спектрограммах. Это проложило путь к широкому набору характеристик в частотной области, включая отношение энергии полосы, спектральный центроид, ширину полосы и коэффициент тональности, каждый из которых можно эффективно использовать для измерения конкретной характеристики входного звука. Существует гораздо больше приложений для обработки сигналов, включая мел-спектрограммы, кепстральные коэффициенты, контроль шума, синтез звука и т. д. Я надеюсь, что это объяснение послужит основой для дальнейшего изучения передовых концепций в этой области.

Надеюсь, вам понравилось читать эту статью! Если у вас есть какие-либо сомнения или предложения, ответьте в поле для комментариев.

Пожалуйста, не стесняйтесь обращаться ко мне через почту.

Если вам понравилась моя статья и вы хотите прочитать о них больше, подписывайтесь на меня.

Примечание. Все изображения (кроме обложки) сделаны автором.

Рекомендации

Крест-фактор. (2023). В Википедии. https://en.wikipedia.org/w/index.php?title=Crest_factor&oldid=1158501578

librosa — документация по Librosa 0.10.1dev. (н.д.). Получено 5 июня 2023 г. с https://librosa.org/doc/main/index.html.

Спектральная плоскостность. (2022). В Википедии. https://en.wikipedia.org/w/index.php?title=Spectral_flatness&oldid=1073105086

Звук ИИ. (2020, 1 августа). Валерио Велардо. https://valeriovelardo.com/the-sound-of-ai/