Документация Engee
Notebook

Анализ ширины полосы 99% мощности сигнала: сравнение двух методов

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

  1. Проектирования телекоммуникационных систем — для определения необходимой полосы пропускания каналов связи

  2. Инженеров РЧ/СВЧ систем — при проектировании фильтров и усилителей

  3. Специалистов по обработке сигналов — для анализа спектральной эффективности методов модуляции

  4. Исследователей в области акустики и вибраций — для характеристики частотного состава сигналов

В данной статье мы сравниваем два подхода к вычислению 99% полосы мощности:

  1. Метод прямого БПФ с использованием базовых функций цифровой обработки сигналов (ЦОС)

  2. Метод с использованием спектр-анализатора из библиотеки EngeeDSP

In [ ]:
using EngeeDSP
using LinearAlgebra
using Statistics

Далее перейдём к самой реализации, нами были разработаны 3 основные функции:

  1. calculate_99_percent_bandwidth - Вычисляет ширину полосы 99% мощности сигнала. Возвращает три параметра: общую ширину, симметричную ширину и центральную частоту. Добавлен параметр band_range для ограничения диапазона анализа.

  2. bandwidth_99_direct - Прямой расчет БПФ с методом Уэлча для оценки спектра мощности. Использует параметр band_range для фильтрации частотного диапазона перед расчетом.

  3. bandwidth_99_spectrumanalyzer - Использует встроенный спектр-анализатор EngeeDSP. Добавлены параметры band_range (фильтрация результатов) и freq_span (настройка диапазона анализатора).

Функция calculate_99_percent_bandwidth

Функция вычисляет ширину полосы частот, содержащей 99% общей мощности сигнала. Принимает массив частот frequencies, соответствующий массив мощности power и опциональный параметр band_range для ограничения анализируемого диапазона.

Алгоритм работы:

  1. Фильтрация по диапазону: Если задан band_range, функция оставляет только частоты и значения мощности в указанных границах. При отсутствии данных в диапазоне возвращается ошибка.

  2. Нормализация мощности: Вычисляется общая мощность, значения нормализуются так, чтобы их сумма равнялась 1. Это позволяет работать с процентными соотношениями.

  3. Вычисление основной ширины полосы:

    • Мощность сортируется по убыванию
    • Находится минимальный набор компонентов, кумулятивная сумма которых достигает 99%
    • Ширина полосы вычисляется как разница между максимальной и минимальной частотой в этом наборе
  4. Вычисление симметричной ширины полосы:

    • Частоты сортируются по возрастанию
    • Находятся частоты, соответствующие 0,5% и 99,5% кумулятивной мощности
    • Разница между этими частотами даёт симметричную ширину полосы
  5. Вычисление центроида: Определяется средневзвешенная частота, где весами выступают относительные значения мощности.

Возвращаемые значения: общая ширина полосы, симметричная ширина полосы, центроид частоты.

In [ ]:
function calculate_99_percent_bandwidth(frequencies, power; band_range=nothing)
    if band_range !== nothing
        min_freq, max_freq = band_range
        idx = findall(x -> min_freq <= x <= max_freq, frequencies)
        if isempty(idx)
            error("Нет данных в указанном диапазоне частот [$min_freq, $max_freq] Гц")
        end
        frequencies = frequencies[idx]
        power = power[idx]
    end
    total_power = sum(power)
    if total_power == 0
        return 0.0, 0.0, 0.0
    end
    normalized_power = power / total_power
    sorted_indices = sortperm(normalized_power, rev=true)
    cumulative = cumsum(normalized_power[sorted_indices])
    idx_99 = findfirst(x -> x >= 0.99, cumulative)
    if idx_99 === nothing
        idx_99 = length(cumulative)
    end
    freq_indices_99 = sorted_indices[1:idx_99]
    freq_values = frequencies[freq_indices_99]
    bandwidth = maximum(freq_values) - minimum(freq_values)
    freq_order = sortperm(frequencies)
    cumulative_by_freq = cumsum(normalized_power[freq_order])
    idx_low = findfirst(x -> x >= 0.005, cumulative_by_freq)
    idx_high = findfirst(x -> x >= 0.995, cumulative_by_freq)
    if idx_low !== nothing && idx_high !== nothing
        freq_sorted = frequencies[freq_order]
        bandwidth_symmetric = freq_sorted[idx_high] - freq_sorted[idx_low]
    else
        bandwidth_symmetric = bandwidth
    end
    centroid = sum(frequencies .* normalized_power) / sum(normalized_power)
    return bandwidth, bandwidth_symmetric, centroid
end
Out[0]:
calculate_99_percent_bandwidth (generic function with 1 method)

Функция bandwidth_99_direct

Функция вычисляет 99% полосу мощности сигнала методом Уэлча (усреднение периодограмм). Принимает сигнал, частоту дискретизации и опциональный параметр band_range для ограничения частотного диапазона.

Алгоритм работы:

  1. Параметры обработки:

    • Размер БПФ: 1024 точки или длина сигнала (если меньше)
    • Сигнал делится на неперекрывающиеся сегменты для усреднения
  2. Обработка сегментов:

    • Сегменты короче 64 отсчетов игнорируются
    • Применяется окно Ханна для уменьшения спектрального утечения
    • Выполняется БПФ оконного сегмента
    • Вычисляется периодограмма с нормировкой на частоту дискретизации и энергию окна
    • Из двустороннего спектра извлекается односторонний (положительные частоты) с удвоением амплитуд для сохранения мощности
  3. Усреднение: Периодограммы всех сегментов усредняются

  4. Создание частотной оси: Генерируются частоты от 0 до частоты Найквиста

  5. Фильтрация по диапазону: Если задан band_range, оставляются только частоты и значения PSD в указанном диапазоне

  6. Расчет параметров: Вызывается функция calculate_99_percent_bandwidth для вычисления ширины полосы, симметричной ширины и центроида

Возвращаемые значения: общая ширина полосы, симметричная ширина полосы, центроид, массив частот, массив PSD.

In [ ]:
function bandwidth_99_direct(signal, sample_rate; band_range=nothing)
    nfft = min(1024, length(signal))
    segments = max(1, floor(Int, length(signal) / nfft))
    psd_total = zeros(nfft ÷ 2 + 1)
    for i in 1:segments
        start_idx = (i-1)*nfft + 1
        end_idx = min(i*nfft, length(signal))
        segment = signal[start_idx:end_idx]
        if length(segment) < 64
            continue
        end
        window = 0.5 .- 0.5*cos.(2π*(0:length(segment)-1)/(length(segment)-1))
        windowed = segment .* window
        fft_result = EngeeDSP.fft(windowed)
        psd = abs2.(fft_result) / (sample_rate * sum(window.^2))
        if length(psd) % 2 == 0
            idx = 1:length(psd)÷2 + 1
        else
            idx = 1:(length(psd)+1)÷2
        end
        psd_segment = psd[idx]
        if length(psd_segment) > 2
            psd_segment[2:end-1] .*= 2
        end
        if length(psd_segment) <= length(psd_total)
            psd_total[1:length(psd_segment)] .+= psd_segment
        end
    end
    psd_avg = psd_total ./ segments
    frequencies = range(0, sample_rate/2, length=length(psd_avg))
    if band_range !== nothing
        min_freq, max_freq = band_range
        idx = findall(x -> min_freq <= x <= max_freq, frequencies)
        if !isempty(idx)
            frequencies = frequencies[idx]
            psd_avg = psd_avg[idx]
        end
    end
    bandwidth, bandwidth_sym, centroid = calculate_99_percent_bandwidth(frequencies, psd_avg)
    return bandwidth, bandwidth_sym, centroid, frequencies, psd_avg
end
Out[0]:
bandwidth_99_direct (generic function with 1 method)

Функция bandwidth_99_spectrumanalyzer

Функция вычисляет 99% полосу мощности сигнала с использованием встроенного спектр-анализатора EngeeDSP. Принимает сигнал, частоту дискретизации и два опциональных параметра: band_range для фильтрации результатов и freq_span для настройки диапазона анализатора.

Алгоритм работы:

  1. Настройка анализатора:

    • Если задан freq_span, анализатор конфигурируется для работы в указанном диапазоне
    • В противном случае используется полный диапазон (0 - fs/2)
    • Применяются: метод Уэлча, окно Ханна, автоматическая настройка RBW
  2. Анализ сигнала: Сигнал передается анализатору для обработки

  3. Получение данных: Извлекаются массивы частот и значений спектра

  4. Фильтрация по диапазону: Если задан band_range, оставляются только частоты и значения спектра в указанных границах

  5. Расчет параметров: Вызывается функция calculate_99_percent_bandwidth для вычисления ширины полосы, симметричной ширины и центроида

  6. Освобождение ресурсов: Анализатор освобождается после использования

Возвращаемые значения: общая ширина полосы, симметричная ширина полосы, центроид, массив частот, массив спектра.

In [ ]:
function bandwidth_99_spectrumanalyzer(signal, sample_rate; band_range=nothing, freq_span=nothing)
    if freq_span !== nothing
        span_start, span_end = freq_span
        scope = EngeeDSP.spectrumAnalyzer(
            SampleRate = sample_rate,
            SpectrumType = "power",
            SpectrumUnits = "Watts",
            Method = "welch",
            Window = "Hann",
            RBWSource = "auto",
            FrequencySpan = "span-and-center-frequency",
            Span = span_end - span_start,
            CenterFrequency = (span_start + span_end) / 2,
            PlotAsTwoSidedSpectrum = false,
            AveragingMethod = "exponential",
            ForgettingFactor = 1.0,
            VBWSource = "auto",
            OverlapPercent = 0
        )
    else
        scope = EngeeDSP.spectrumAnalyzer(
            SampleRate = sample_rate,
            SpectrumType = "power",
            SpectrumUnits = "Watts",
            Method = "welch",
            Window = "Hann",
            RBWSource = "auto",
            FrequencySpan = "full",
            PlotAsTwoSidedSpectrum = false,
            AveragingMethod = "exponential",
            ForgettingFactor = 1.0,
            VBWSource = "auto",
            OverlapPercent = 0
        )
    end
    scope(signal)
    spectrum_data = getSpectrumData(scope)
    if haskey(spectrum_data, "spectrum") && haskey(spectrum_data, "frequencies")
        spectrum = spectrum_data["spectrum"]
        frequencies = spectrum_data["frequencies"]
        if ndims(spectrum) > 1
            spectrum = vec(mean(spectrum, dims=2))
        end
        if ndims(frequencies) > 1
            frequencies = vec(frequencies)
        end
        if band_range !== nothing
            min_freq, max_freq = band_range
            idx = findall(x -> min_freq <= x <= max_freq, frequencies)
            if !isempty(idx)
                frequencies = frequencies[idx]
                spectrum = spectrum[idx]
            end
        end
        bandwidth, bandwidth_sym, centroid = calculate_99_percent_bandwidth(frequencies, spectrum)
        release!(scope)
        return bandwidth, bandwidth_sym, centroid, frequencies, spectrum
    else
        release!(scope)
        error("Не удалось получить данные спектра")
    end
end
Out[0]:
bandwidth_99_spectrumanalyzer (generic function with 1 method)

Тестирование разработанных алгоритмов

Данный тест демонстрирует сравнение двух подходов к вычислению полосы 99% мощности сигнала на примере одного тестового сигнала, состоящего из трёх синусоидальных компонент (1000 Гц, 2500 Гц, 4000 Гц) с добавлением белого шума.

Тест последовательно выполняет четыре варианта анализа:

  1. Базовый вариант - прямое вычисление БПФ без ограничений по частоте
  2. Прямой метод с ограничением полосы - использование параметра band_range для фильтрации частотного диапазона
  3. Метод спектр-анализатора с ограничением - применение встроенного инструмента с фильтрацией результатов
  4. Метод спектр-анализатора с настройкой диапазона - использование параметра freq_span для конфигурации самого анализатора

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

In [ ]:
function generate_test_signal(fs, duration)
    t = range(0, duration, step=1/fs)
    f1 = 1000
    f2 = 2500
    f3 = 4000
    signal = 1.0 * EngeeDSP.sin.(2π * f1 * t) +
             0.5 * EngeeDSP.sin.(2π * f2 * t) +
             0.2 * EngeeDSP.sin.(2π * f3 * t) +
             0.03 * randn(length(t))
    return signal
end

fs = 10000
duration = 0.1
signal = generate_test_signal(fs, duration)
println("\n1. Полный диапазон частот:")
bw1, bw1_sym, centroid1, freqs1, power1 = bandwidth_99_direct(signal, fs)
println("   Ширина полосы: $(round(bw1, digits=2)) Гц")
println("   Симметричная ширина: $(round(bw1_sym, digits=2)) Гц")
println("   Центральная частота: $(round(centroid1, digits=2)) Гц")
println("\n2. Ограниченный диапазон [500, 3000] Гц:")
bw2, bw2_sym, centroid2, freqs2, power2 = bandwidth_99_direct(signal, fs; band_range=(500.0, 3000.0))
println("   Ширина полосы: $(round(bw2, digits=2)) Гц")
println("   Симметричная ширина: $(round(bw2_sym, digits=2)) Гц")
println("   Центральная частота: $(round(centroid2, digits=2)) Гц")
println("\n3. SpectrumAnalyzer с диапазоном [1000, 3500] Гц:")
bw3, bw3_sym, centroid3, freqs3, power3 = bandwidth_99_spectrumanalyzer(signal, fs; band_range=(1000.0, 3500.0))
println("   Ширина полосы: $(round(bw3, digits=2)) Гц")
println("   Симметричная ширина: $(round(bw3_sym, digits=2)) Гц")
println("   Центральная частота: $(round(centroid3, digits=2)) Гц")
println("\n4. SpectrumAnalyzer с настройкой freq_span [500, 4500] Гц:")
bw4, bw4_sym, centroid4, freqs4, power4 = bandwidth_99_spectrumanalyzer(signal, fs; freq_span=(500.0, 4500.0))
println("   Ширина полосы: $(round(bw4, digits=2)) Гц")
println("   Симметричная ширина: $(round(bw4_sym, digits=2)) Гц")
println("   Центральная частота: $(round(centroid4, digits=2)) Гц")
1. Полный диапазон частот:
   Ширина полосы: 3020.0 Гц
   Симметричная ширина: 3020.0 Гц
   Центральная частота: 1385.74 Гц

2. Ограниченный диапазон [500, 3000] Гц:
   Ширина полосы: 1520.0 Гц
   Симметричная ширина: 1520.0 Гц
   Центральная частота: 1299.55 Гц

3. SpectrumAnalyzer с диапазоном [1000, 3500] Гц:
   Ширина полосы: 1604.82 Гц
   Симметричная ширина: 1582.03 Гц
   Центральная частота: 1542.26 Гц

4. SpectrumAnalyzer с настройкой freq_span [500, 4500] Гц:
   Ширина полосы: 3778.65 Гц
   Симметричная ширина: 3322.92 Гц
   Центральная частота: 1380.77 Гц
In [ ]:
p1 = plot(freqs1, 10*log10.(power1 .+ 1e-10), 
         label="Полный диапазон", 
         linewidth=2,
         xlabel="Частота (Гц)", 
         ylabel="Мощность (дБ)",
         title="Прямой БПФ - Полный диапазон",
         grid=true)
display(p1)
p2 = plot(freqs2, 10*log10.(power2 .+ 1e-10), 
         label="[500, 3000] Гц", 
         linewidth=2,
         xlabel="Частота (Гц)", 
         ylabel="Мощность (дБ)",
         title="Прямой БПФ - Ограниченный диапазон",
         grid=true)
display(p2)
p3 = plot(freqs3, 10*log10.(power3 .+ 1e-10), 
         label="SpectrumAnalyzer [1000, 3500] Гц", 
         linewidth=2,
         xlabel="Частота (Гц)", 
         ylabel="Мощность (дБ)",
         title="SpectrumAnalyzer с фильтрацией band_range",
         grid=true)
display(p3)
p4 = plot(freqs4, 10*log10.(power4 .+ 1e-10), 
         label="SpectrumAnalyzer с freq_span [500, 4500] Гц", 
         linewidth=2,
         xlabel="Частота (Гц)", 
         ylabel="Мощность (дБ)",
         title="SpectrumAnalyzer с настройкой freq_span",
         grid=true)
display(p4)
In [ ]:
p5 = bar(["Полный", "[500,3000]", "SA [1000,3500]", "SA span [500,4500]"], 
         [bw1_sym, bw2_sym, bw3_sym, bw4_sym],
         xlabel="Метод",
         ylabel="Ширина полосы (Гц)",
         title="Сравнение симметричной ширины полосы 99%",
         color=[:blue :red :green :purple],
         legend=false,
         xtickfontsize=8)
Out[0]:

Сравнение четырёх методов анализа показывает существенное влияние ограничения частотного диапазона на измеряемые параметры ширины полосы. Полный диапазон частот (тест 1) демонстрирует максимальную ширину полосы 3020 Гц, охватывая все три синусоидальные компоненты тестового сигнала.

Ограничение диапазона анализа с помощью параметра band_range (тесты 2 и 3) приводит к значительному уменьшению измеряемой ширины полосы (до 1520 Гц и 1604 Гц соответственно), поскольку исключаются частотные компоненты за пределами указанных границ. При этом центральная частота смещается в зависимости от выбранного диапазона.

Использование спектр-анализатора (тесты 3 и 4) дало более точные и детализированные результаты по сравнению с прямым методом БПФ. Особенно показателен тест 4 с параметром freq_span, который предоставил наиболее полную характеристику сигнала (ширина полосы 3778 Гц), что ближе к теоретически ожидаемому значению для трёхкомпонентного сигнала. Спектр-анализатор также демонстрирует различие между общей и симметричной шириной полосы, что указывает на более тонкий учёт распределения мощности в частотной области.

Вывод

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