быстрое преобразование фурье что такое
Принцип построения алгоритмов быстрого преобразования Фурье
DSPL-2.0 — свободная библиотека алгоритмов цифровой обработки сигналов
Распространяется под лицензией LGPL v3
Дискретное преобразование Фурье (ДПФ), на сегодняшний день, один из распространенных инструментов анализа, который применяется во всех отраслях науки и техники. Однако до появления компьютеров ДПФ использовалось редко, поскольку вычисление 32-точечного ДПФ требует 1024 операции комплексного умножения и сложения.
Первое упоминание об алгоритме разложения функций в тригонометрический ряд, в котором использовались свойства периодичности тригонометрических функций для ускоренного расчета, относится к работам Гаусса [1]. На эту работу долгое время никто не обращал внимания, до тех пор пока алгоритмы быстрого преобразования Фурье (БПФ) не получили широкое распространение.
Первая программная реализация алгоритма БПФ была осуществлена в начале 60-х годов XX века Джеймсом Кули в вычислительном центре IBM под руководством Джона Тьюки, а в 1965 году, ими же, была опубликована статья [2], посвященная алгоритму быстрого преобразования Фурье. С этого момента начинается настоящая БПФ-мания. Публикуется множество работ, посвященных алгоритму БПФ, одна за одной выходят монографии, программисты соревнуются в эффективности реализации алгоритма. БПФ становится основным инструментом спектрального анализа сигналов.
Сегодня данный принцип построения быстрых алгоритмов носит название «разделяй и властвуй» [4] (англ. divide and conquer) и используется для широкого круга задач: БПФ, быстрого поиска, быстрого матричного умножения и других.
Рассмотрим выражение для дискретного преобразования Фурье:
Эффективный алгоритм вычисления прямого БПФ можно использовать и для обратного преобразования.
Обратим внимание, что комплексные экспоненты в выражениях для прямого и обратного ДПФ являются комплексно-сопряженными:
Если вместо ДПФ использовать БПФ, то получим обратное быстрое преобразование Фурье (ОБПФ). При этом для выполнения комплексного сопряжения необходимо лишь поменять знак перед мнимой частью спектра до вызова функции БПФ и результата после БПФ.
Таким образом, мы рассмотрели путь ускорения вычислений при расчете ДПФ, а также свели ОДПФ к прямому. Теперь необходимо рассмотреть способы разделения-объединения, обеспечивающие существенное сокращение вычислений.
Понимание алгоритма БПФ
Здравствуйте, друзья. Уже завтра стартует курс «Алгоритмы для разработчиков», а у нас остался один неопубликованный перевод. Собственно исправляемся и делимся с вами материалом. Поехали.
Быстрое преобразование Фурье (БПФ — англ. FFT) является одним из важнейших алгоритмов обработки сигналов и анализа данных. Я пользовался им годами, не имея формальных знаний в области компьютерных наук. Но на этой неделе мне пришло в голову, что я никогда не задавался вопросом, как БПФ так быстро вычисляет дискретное преобразование Фурье. Я стряхнул пыль со старой книги по алгоритмам, открыл ее, и с удовольствием прочитал об обманчиво простой вычислительной уловке, которую Дж. В. Кули и Джон Тьюки описали в своей классической работе 1965 года, посвященной этой теме.
Цель этого поста — окунуться в алгоритм БПФ Кули-Тьюки, объясняя симметрии, которые к нему приводят, и показать несколько простых реализаций на Python, применяющих теорию на практике. Я надеюсь, что это исследование даст специалистам по анализу данных, таким как я, более полную картину того, что происходит под капотом используемых нами алгоритмов.
Дискретное преобразование Фурье
БПФ — это быстрый алгоритм для вычисления дискретного преобразования Фурье (ДПФ), которое напрямую вычисляется за
. ДПФ, как и более знакомая непрерывная версия преобразования Фурье, имеет прямую и обратную форму, которые определяются следующим образом:
Прямое дискретное преобразование Фурье (ДПФ):
Обратное дискретное преобразование Фурье (ОДПФ):
Преобразование из xn → Xk является переводом из конфигурационного пространства в пространство частотное и может быть очень полезным как для исследования спектра мощности сигнала, так и для преобразования определенных задач для более эффективного вычисления. Некоторые примеры этого в действии вы можете найти в главе 10 нашей будущей книги по астрономии и статистике, где также можно найти изображения и исходный код на Python. Пример использования БПФ для упрощения интегрирования сложных в противном случае дифференциальных уравнений смотрите в моем посте «Решение уравнения Шредингера в Python».
Из-за важности БПФ (далее может быть использовано равносильное FFT — Fast Fourier Transform) во многих областях Python содержит множество стандартных инструментов и оболочек для его вычисления. И NumPy, и SciPy имеют оболочки из чрезвычайно хорошо протестированной библиотеки FFTPACK, которые находятся в подмодулях numpy.fft и scipy.fftpack соответственно. Самый быстрый БПФ, о котором я знаю, находится в пакете FFTW, который также доступен в Python через пакет PyFFTW.
На данный момент, однако, давайте оставим эти реализации в стороне и зададимся вопросом, как мы можем вычислить БПФ в Python с нуля.
Вычисление дискретного преобразования Фурье
Для простоты мы будем касаться только прямого преобразования, поскольку обратное преобразование может быть реализовано очень похожим образом. Взглянув на приведенное выше выражение ДПФ (DFT), мы видим, что это не более чем прямолинейная линейная операция: умножение матрицы на вектор
с матрицей М, заданной
Имея это в виду, мы можем вычислить ДПФ с использованием простого умножения матрицы следующим образом:
Мы можем перепроверить результат, сравнив его со встроенной в numpy БПФ-функцией:
Просто чтобы подтвердить медлительность нашего алгоритма, мы можем сравнить время выполнения этих двух подходов:
Мы более чем в 1000 раз медленнее, что и следовало ожидать для такой упрощенной реализации. Но это не самое худшее. Для входного вектора длины N алгоритм БПФ масштабируется как , в то время как наш медленный алгоритм масштабируется как
. Это означает, что для
элементов мы ожидаем, что БПФ завершится за где-то около 50 мс, в то время как наш медленный алгоритм займет около 20 часов!
Так как же БПФ добивается такого ускорения? Ответ заключается в использовании симметрии.
Симметрии в дискретном преобразовании Фурье
Одним из наиболее важных инструментов в построении алгоритмов является использование симметрий задачи. Если вы можете аналитически показать, что одна часть проблемы просто связана с другой, вы можете вычислить подрезультат только один раз и сэкономить эти вычислительные затраты. Кули и Тьюки использовали именно этот подход при получении БПФ.
Мы начнем с вопроса о значении . Из нашего выражения выше:
где мы использовали тождество exp [2π i n] = 1, которое выполняется для любого целого числа n.
Последняя строка хорошо показывает свойство симметрии ДПФ:
для любого целого числа i. Как мы увидим ниже, эту симметрию можно использовать для гораздо более быстрого вычисления ДПФ.
ДПФ в БПФ: использование симметрии
Кули и Тьюки показали, что можно разделить вычисления БПФ на две меньшие части. Из определения ДПФ имеем:
Мы разделили одно дискретное преобразование Фурье на два слагаемых, которые сами по себе очень похожи на меньшие дискретные преобразования Фурье, одно на значения с нечетным номером и одно на значения с четным номером. Однако до сих пор мы не сохранили никаких вычислительных циклов. Каждый член состоит из (N / 2) ∗ N вычислений, всего .
Хитрость заключается в использовании симметрии в каждом из этих условий. Поскольку диапазон k равен 0≤k True
Сопоставим этот алгоритм с нашей медленной версией:
-In [6]:
Наш расчет быстрее чем прямая версия на порядок! Более того, наш рекурсивный алгоритм асимптотически : мы реализовали быстрое преобразование Фурье.
Обратите внимание, что мы все еще не приблизились к скорости встроенного алгоритма FFT в numpy, и этого следовало ожидать. Алгоритм FFTPACK, стоящий за fft numpy, — это реализация на Фортране, которая получила годы доработок и оптимизаций. Кроме того, наше решение NumPy включает в себя как рекурсию стека Python, так и выделение множества временных массивов, что увеличивает время вычислений.
Хорошая стратегия для ускорения кода при работе с Python / NumPy — по возможности векторизовать повторяющиеся вычисления. Это мы можем сделать — в процессе удалять наши рекурсивные вызовы функций, что сделает наш Python FFT еще более эффективным.
Обратите внимание, что в вышеупомянутой рекурсивной реализации FFT на самом низком уровне рекурсии мы выполняем N / 32 идентичных матрично-векторных произведений. Эффективность нашего алгоритма выиграет, если одновременно вычислить эти матрично-векторные произведения как единое матрично-матричное произведение. На каждом последующем уровне рекурсии мы также выполняем повторяющиеся операции, которые можно векторизовать. NumPy отлично справляется с такой операцией, и мы можем использовать этот факт для создания этой векторизованной версии быстрого преобразования Фурье:
Хотя алгоритм немного более непрозрачен, это просто перестановка операций, используемых в рекурсивной версии, с одним исключением: мы используем симметрию в вычислении коэффициентов и строим только половину массива. Опять же, мы подтверждаем, что наша функция дает правильный результат:
Поскольку наши алгоритмы становятся намного более эффективными, мы можем использовать больший массив для сравнения времени, оставляя DFT_slow :
In [9]:
Мы улучшили нашу реализацию еще на порядок! Сейчас мы находимся на расстоянии примерно в 10 раз от эталона FFTPACK, используя всего пару десятков строк чистого Python + NumPy. Хотя это все еще не соответствует в вычислительном отношении, с точки зрения читаемости версия Python намного превосходит исходный код FFTPACK, который вы можете просмотреть здесь.
Итак, как FFTPACK достигает этого последнего ускорения? Ну, в основном, это просто вопрос детальной бухгалтерии. FFTPACK тратит много времени на повторное использование любых промежуточных вычислений, которые можно использовать повторно. Наша клочковатая версия все еще включает в себя избыток выделения памяти и копирования; на низкоуровневом языке, таком как Fortran, легче контролировать и минимизировать использование памяти. Кроме того, алгоритм Кули-Тьюки можно расширить, чтобы использовать разбиения размером, отличным от 2 (то, что мы здесь реализовали, известно как БПФ Кули-Тьюки радикса по основе 2). Также могут быть использованы другие более сложные алгоритмы БПФ, в том числе принципиально отличные подходы, основанные на сверточных данных (см., Например, алгоритм Блюштейна и алгоритм Рейдера). Комбинация вышеупомянутых расширений и методов может привести к очень быстрым БПФ даже на массивах, размер которых не является степенью двойки.
Хотя функции на чистом Python, вероятно, бесполезны на практике, я надеюсь, что они преподнесли некоторую интуицию в том, что происходит на фоне анализа данных на основе FFT. Как специалисты по данным, мы можем справиться с реализацией «черного ящика» фундаментальных инструментов, созданных нашими более алгоритмически настроенными коллегами, но я твердо убежден, что чем больше у нас понимания о алгоритмах низкого уровня, которые мы применяем к нашим данным, тем лучшими практиками мы будем.
Этот пост был полностью написан в блокноте IPython. Полный блокнот можно скачать здесь или посмотреть статически здесь.
Многие могут заметить, что материал далеко не новый, но, как нам кажется, вполне актуальный. В общем пишите была ли статья полезной. Ждём ваши комментарии.
Преобразование Фурье: самый подробный разбор
Преобразование Фурье – одно из базовых понятий в обработке сигналов и анализе данных. Но что оно означает? Геометрическая интерпретация.
Возьмём классическую задачу – работу со звуком. Теперь добавим конкретики.
Ваш друг приносит запись своего живого выступления. И это очень удачное выступление. Но! Хотя запись делали на хороший микрофон, в ней всё равно присутствует шум. Друг просит помочь убрать его или хотя бы уменьшить.
Здесь и пригодится знание преобразования Фурье.
Что такое звук в математическом смысле?
Отдельная нота – это гармонический сигнал с определённой частотой и амплитудой.
Как правило, мелодию, речь или иной звуковой сигнал можно представить как сумму гармонических сигналов. Шумом в таком случае мы называем слагаемые, соответствующие любым нежелательным звукам.
Преобразование Фурье позволяет разложить исходный сигнал на гармонические составляющие, что потребуется для выделения шумов.
Здесь g(t) – это исходный сигнал (в нашем случае запись друга). В контексте преобразования Фурье его называют оригиналом. G(f) – изображение по Фурье, а параметром f выступает частота.
Возможно, вам уже знакомо это определение. Но знаете ли вы, как происходит это преобразование? Если бы увидели его впервые, поняли бы, как с его помощью анализировать исходный сигнал?
Геометрическая интерпретация преобразования Фурье
Грант Сандерсон предлагает геометрический аналог преобразования Фурье. За несколько графических переходов от исходного сигнала к изображению каждая из компонент определения обретает смысл, а само преобразование получает новое геометрическое прочтение.
В дальнейшем обсуждении предполагается, что вы знакомы с векторами, интегрированием и понятием комплексного числа. Если каких-то знаний вам всё-таки не хватает, ознакомьтесь с материалами из нашей подборки по вузовской математике.
1. Наматываем сигнал
Отобразим g(t) на комплексную плоскость. Для этого введём радиус-вектор, который равномерно вращается по часовой стрелке. Его длина в каждый момент времени равна модулю значения сигнала, а частота вращения выбирается произвольным образом.
Теперь построим траекторию движения конца вектора, совершающего полный оборот за две секунды, или, другими словами, с частотой вращения fВ = 0.5 об/с.
Выглядит, будто мы намотали исходный сигнал на начало координат. В минимумах сигнала полученная «намотка» сливается с началом координат, а при приближении к максимумам – отклоняется.
Пока выглядит не особо информативно, не так ли?
А теперь увеличим частоты намотки.
Сначала график распределяется довольно симметрично относительно начала координат до частоты вращения fВ = 3 об/с. Затем максимумы резко смещаются в правую полуплоскость, а намотка перестаёт напоминать узор спирографа.
2. Ищем центр масс
Посмотрим внимательнее, что происходит. В качестве характеристики намотки возьмём усреднённое значение всех её точек – центр масс (отметим его оранжевым цветом).
Строим зависимость положения центра масс от частоты намотки. Сейчас нам достаточно рассмотреть х-кординату, но в дальнейшем для определения преобразования Фурье потребуются обе координаты.
Тогда что означает всплеск на низких частотах?
3. Анализируем влияние смещения
Возможно, вы обратили внимание, что рассматриваемый нами сигнал смещён на единицу. Сдвиг был введён для наглядности, но именно он приводит к усложнению поведения центра масс.
При нулевой частоте всё отображение сигнала на комплексной плоскости располагается на оси абсцисс. На малых частотах намотка по-прежнему группируется в правой полуплоскости.
Как только мы убираем сдвиг, т. е. берём сигнал вида g(t) = cos (6πt), намотка при низких частотах сдвигается влево по оси абсцисс.
Построение радиус-вектора остаётся аналогичным. Его длина равна модулю значения сигнала, направление вращения – положительное. Но при смене знака g(t) направление вектора меняется на противоположное.
Сейчас вы увидите, как меняется намотка и х-координата центра масс несмещённого сигнала.
Таким образом, на графике остался только один резкий скачок.
Это важный момент при использовании преобразования Фурье: линейный тренд и смещение проявляются на низких частотах, потому их исключают из исходного сигнала.
4. Выделяем частоты полигармонического сигнала
Мы наблюдаем два пика в точках fВ = 2 об/с и fВ = 3 об/с, что соответствует частотному составу исходной суммы.
Отметим ещё один интересный факт, верный как для х-координаты, так и для преобразования Фурье. Преобразование для суммы сигналов и сумма преобразований сигналов имеют один и тот же вид. Т. е. преобразование Фурье линейно.
Таким образом, этот подход позволяет определить частоту колебаний как моно-, так и полигармонического сигнала. Осталось математически описать процедуру вычисления центра масс намотки.
Вывод преобразования Фурье
В самом начале рассмотрения мы отобразили исходный сигнал на комплексную плоскость. Такой выбор не случаен – это позволяет рассматривать точки на плоскости как комплексные числа и использовать формулу Эйлера для описания намотки:
Геометрически это соотношение означает, что при любом φ точка e iφ на комплексной плоскости лежит на единичной окружности.
Построим радиус-вектор e iφ при разных значениях φ.
При изменении φ на 2π вектор проходит полный оборот против часовой стрелки, так как 2π – длина единичной окружности. Чтобы задать скорость вращения вектора, показатель степени домножаем на ft, а для смены направления вращения – на -1.
Теперь вычисляем центр масс. Для этого отметим N произвольных точек на графике намотки и вычислим среднее:
Если мы будем увеличивать количество рассматриваемых точек, придём к предельному случаю:
где t1 и t2 – границы интервала, на котором рассматривается сигнал.
Выражение перед интегралом представляет собой масштабирующий коэффициент, но не отражает поведение центра масс. Потому его можно отбросить.
Полученное выражение и будет являться преобразованием Фурье с той разницей, что в общем виде интегрирование задаётся на интервале от -∞ до +∞.
Такой переход к бесконечному интервалу означает, что мы не накладываем никаких ограничений на длительность рассматриваемого сигнала.
Применение преобразования Фурье для фильтрации
Теперь, говоря о преобразовании Фурье, вы можете представлять его геометрическую интерпретацию – намотку сигнала на комплексную плоскость и вычисление центр масс.
При этом частота намотки f становится входным параметром для изображения по Фурье. Центр масс выступает оценкой, насколько хорошо соотносится (коррелирует) параметр f с присутствующими в сигнале частотами.
После того, как вы найдёте в принесённой другом записи все частотные компоненты, вам останется только вычесть их из изображения и применить обратное преобразование Фурье.
Практическое применение преобразования Фурье для обработки сигналов
Книги и публикации по цифровой обработке сигналов пишут авторы зачастую не догадывающиеся и не понимающие задач, стоящих перед разработчиками. Особенно это касается систем, работающих в реальном времени. Эти авторы отводят себе скромную роль бога, существующего вне времени и пространства, что вызывает некоторое недоумение у читателей подобной литературы. Данная публикация имеет целью развеять недоумения, возникающие у большинства разработчиков, и помочь им преодолеть «порог вхождения», для этих целей в тексте сознательно используется аналогии и терминология сферы программирования.
Данный опус не претендует на полноту и связность изложения.
Добавлено после прочтения комментариев.
Публикаций о том как делать БПФ немеряно, а о том как сделать БПФ, преобразовать спектр, и собрать сигнал заново, да еще и в реальном времени, явно не хватает. Автор пытается восполнить этот пробел.
Часть первая, обзорная
Существуют два основных способа построения дискретных линейных динамических систем. В литературе, такие системы принято называть цифровыми фильтрами, которые подразделяются на два основных типа: фильтры с конечной импульсной характеристикой (КИХ) и фильтры с бесконечной импульсной характеристикой (БИХ).
Алгоритмическая сущность фильтра с КИХ заключается в дискретном вычислении интеграла свертки:
Где x(t) – входной сигнал
y(t) – выходной сигнал
h(t) – импульсная характеристика фильтра или реакция фильтра на дельта функцию. Импульсная характеристика является обратным преобразованием Фурье комплексной частотной характеристики фильтра K(f).
Для формирования ясной картины у читателя, приведем пример дискретного вычисления интеграла свертки на языке С в реальном времени.
Вызывая данную функцию через определенные интервалы времени T и передавая ей в качестве аргумента входной сигнал, на выходе мы получим выходной сигнал, соответствующий реакции фильтра с импульсной характеристикой вида:
h(t)=1 при 0 >alfa);, но в этом случае происходит потеря alfa значащих разрядов. Рекуррентное выражение фильтра, из примера кода, построено таким образом, чтобы избежать потери значащих разрядов. Именно конечная точность вычислений может испортить всю прелесть цифрового фильтра с бесконечной импульсной характеристикой. Особенно это заметно на фильтрах высоких порядков, отличающихся высокой добротностью. В реальных динамических системах такая проблема не возникает, наша Матрица производит вычисления с невероятной для нас точностью.
Синтезу подобных фильтров посвящена масса литературы, также имеются готовые программные продукты (см. выше).
Часть вторая. Фурье – фильтр
Из вузовских курсов (у вашего покорного слуги это был курс ОТЭЦ) многие собравшие помнят два основных подхода к анализу линейных динамических систем: анализ во временной области и анализ в частотной области. Анализ во временной области — это решение дифференциальных уравнений, интегралы свертки и Дюамеля. Эти методы анализа дискретно воплотились в цифровых фильтрах БИХ и КИХ.
Но существует частотный подход к анализу линейных динамических систем. Иногда его называют операторным. В качестве операторов используются преобразование Фурье, Лапласа и т.п. Далее мы будем говорить только о преобразовании Фурье.
Данный метод анализа не получил широкого распространения при построении цифровых фильтров. Автору не удалось найти вменяемых практических рекомендаций по построению подобных фильтров на русском языке. Единственное краткое упоминание такого фильтра в практической литературе [Рабинер Л., Гоулд Б., Теория и применение цифровой обработки сигналов 1978], но в данной книге рассмотрение подобного фильтра очень поверхностно. В указанной книге данная схема построения фильтра называется: «свертка в реальном времени методом БПФ», что, по моему скромному мнению, совершенно не отражает сути, название должно быть коротким, иначе времени на отдых не останется.
Реакция линейной динамической системы есть обратное преобразование Фурье от произведения изображения по Фурье входного сигнала x(t) на комплексный коэффициент передачи K(f):
В практическом плане, данное аналитическое выражение предполагает следующий порядок действий: берем преобразование Фурье от входного сигнала, умножаем результат на комплексный коэффициент передачи, выполняем обратное преобразование Фурье, результатом которого является выходной сигнал. В реальном дискретном времени такой порядок действий выполнить невозможно. Как брать интеграл по времени от минус до плюс бесконечности?! Его можно взять только находясь вне времени…
В дискретном мире для выполнения преобразования Фурье существует инструмент — алгоритм быстрого преобразования Фурье (БПФ). Именно его мы и будем использовать при реализации нашего Фурье-фильтра. Аргументом функции БПФ является массив временных отсчетов из 2^n элементов, результатом два массива длинной 2^n элементов соответствующие действительной и мнимой части преобразования Фурье. Дискретной особенностью алгоритма БПФ является то, что входной сигнал считается периодичным с интервалом 2^n. Это накладывает некоторые ограничения на алгоритм Фурье-фильтра. Если взять последовательность выборок входного сигнала, провести от них БПФ, умножить результат БПФ на комплексный коэффициент передачи фильтра и выполнить обратное преобразование …ничего получится! Выходной сигнал будет иметь огромные нелинейные искажения в окрестности стыков выборок.
Для решения этой проблемы необходимо применить два приема:
Такие функции широко применяются в технике цифровой обработки сигналов, и называть их принято — окнами. По скромному мнению автора лучшим, с практической точки зрения, является окно имени Хана:
На рисунке приведены графики иллюстрирующие свойства окна Хана длинной 2^n=256. Экземпляры окна построены с половинным перекрытием k=128. Как видно все оговоренные выше свойства имеются в наличии.
По просьбам трудящихся, на следующем рисунке приведена схема вычислений Фурье-фильтра, при длине выборки 2^n=8, количество выборок 3. На подобных рисунках очень сложно отобразить процесс вычислений, особенно тяжело показать его цикличность, поэтому мы и ограничились количеством выборок равным трем.
Входной сигнал разбивается на блоки длинной 2^n=8 с перекрытием 50%, от каждого блока берется БПФ, результаты БПФ подвергаются нужной трансформации, берется обратное БПФ, результат обратного БПФ скалярно умножается на окно, после умножения блоки складываются с перекрытием.
При выполнение трансформаций спектра, не стоит забывать о главном свойстве массива БПФ действительных сигналов, первая половина массива БПФ комплексно сопряжена со второй половиной, т.е Re[i]=Re[(1













