Основы цифровой обработки сигналов: Активное шумоподавление, Создание приложения в MATLAB, Обработка на Raspberry Pi
Рассмотрены 3 темы по основам цифровой обработки сигналов: активное шумоподавление, создание приложения в MATLAB, обработка на Raspberry Pi.
В данном посте освещены 3 темы по основам цифровой обработки сигналов:
Активное шумоподавление
Поговорим об активном шумоподавлении при помощи адаптивных фильтров. Давайте для начала кратко опишем принцип подавления шума на примере акустики. Но подобный метод используется и в электрических цепях, и в цифровых сигналах.
В акустических системах активного шумоподавления используется второй динамик, который генерирует волну, обратную по фазе шуму. Таким образом шум и «анти-шум» скалыдваются в противофазе.
На картинке нарисован пример, когда у нас есть динамик с шумом и динамик с анти-шумом, но на практике из основного динамика доносится смесь полезного сигнала с шумом. А второй, вспомогательный динамик, выдаёт анти-шум. Сформировать этот самый обратный по фазе сигнал – не сложная задача. А основной задачей здесь становится выделение шума.
Часто в таких системах используются ДВА микрофона, или сенсора. Первый фиксирует смесь сигнал-шум, а второй – только шум. И всё было бы очень просто, если бы шум в смеси полностью соответствовал фиксируемому. Но это не так. Между тем шумом, который мы собираем на микрофон, и тем, что в смеси, существует корреляция, но они не одинаковые. Вот тут на помощь и приходит адаптивная фильтрация.
В качестве эталонного сигнала в схеме используется смесь сигнал-шум с первого сенсора. А в качестве входа КИХ-фильтра – коррелированный шум со второго сенсора. КИХ-фильтр должен пропустить через себя этот шум и изменить его так, чтобы он стал максимально похож на тот, что замешан с полезным сигналом. Тогда если мы вычтем его из смеси, то разница – это и будет наш полезный сигнал! Опять-таки, на практике мы вряд ли добьёмся полного соответствия шумов, поэтому мы говорим о приближении шума и, как результат, приближении полезного сигнала.
Подобные схемы очень эффективны в том случае, когда сигнал и шум нестационарны, то есть меняются во времени. Ну и когда традиционные методы фильтрации неприменимы. Давайте рассмотрим один из таких случаев!
Допустим, вы находитесь в каком-то шумном месте. И вам захотелось спокойно послушать классическую музыку. Вы надеваете наушники с активным шумоподавлением. Рассмотрим, что происходит в наушнике. Если бы шума не было, то во внутреннюю камеру амбушюра попадала только чистая музыка, которая проходит по кабелю от источника до динамика. Но так как шум у нас есть, мы используем встроенный адаптивный фильтр.
Один из микрофонов фильтра, внешний, фиксирует шум толпы. Этот же шум через корпус и набивку попадает во внутреннюю камеру. Среда прохождения физического сигнала имеет свою собственную характеристику, которую мы хотим приблизить. Внутри камеры музыка и шум замешиваются, тем самым формируя смесь, сигнал d. Эта смесь фиксируется вторым микрофоном, и идёт на адаптивный фильтр. Шум внутри камеры коррелирован с шумом снаружи, фильтр подбирает коэффициенты таким образом, чтобы максимально сопоставить шумы, и выдаёт модель внутреннего шума с инвертированной фазой, сигнал –y. Накладывая этот сигнал на смесь, мы можем получить приближение нашей музыки. Перейдём в MATLAB.
Считаем в рабочее пространство классическую музыку, шум толпы, а также коэффициенты фильтра, которым я предполагаю описать искажение шума. В нашем случае это будет простой фильтр нижних частот. Я думаю, что стенки и набивка подавляют верхние частоты. Прослушаем смесь сигнала с внешним шумом, прошедшим через ФНЧ. Так звучит смесь без шумоподавления. Проанализируем её частотный состав. Откроем инструмент Signal Analyzer.
Загрузим музыку, внешний шум и смесь, укажем для всех сигналов частоту дискретизации, поменяем цвета на более контрастные, отразим сигналы во временной области, а также построим спектры.
Полезный сигнал, наша классическая музыка, красным цветом, перекрывается шумом по частоте и по уровню. Попробуем отсечь свист классическим фильтром нижних частот. Уровень полезного сигнала близок к шуму в районе 500 Гц. Создадим копию вектора noisy, смеси сигнала с шумом, и пропустим её через встроенный фильтр нижних частот с границей полосы пропускания 500 Гц. Поменяем цвет на более контрастный, и наблюдаем явное снижение энергетики сигнала.
Получилось ли у нас отсечь то, что мы хотели? Экспортируем копию, и прослушаем результат обработки. Результат явно неудовлетворительный. Есть гул, да и музыку слышно плохо.
Попробуем расширить полосу пропускания ФНЧ до 1,5 кГц. Мы захватили ещё больше шума. Классическая фильтрация нам здесь не поможет, поэтому воспользуемся адаптивной. В предыдущих публикациях мы немного знакомились с системными объектами, они помогают нам осуществлять потоковую обработку в MATLAB.
Инициализируем объекты для проигрывания аудио, чтения отсчётов сигнала из переменных debussy и whistle, а также создадим объект LMS-фильтра длиной 64 отсчёта с шагом 0,0005. Мне интересно понаблюдать за изменением коэффициентов фильтра во времени, поэтому их значения я буду сохранять на каждом шаге в объект myWeights .
Затем следует основной цикл обработки. Пока мы не закончится объект Source_debussy, мы будем помещать по 100 отсчётов свитса в переменную x, по 100 отсчётов смести музыки с шумом, пропущенным через ФНЧ, в переменную d, передавать их на вход LMS-фильтра, проигрывать сигнал ошибки и сохранять текущее значение коэффициентов.
Запустим цикл. Адаптивный фильтр прекрасно справился с задачей. Можно поиграться со значением stepsize и длиной фильтра, и получить лучший результат, но это я призываю вас сделать самостоятельно. А сейчас осталось только визуализировать изменение коэффициентов фильтра во времени.
Вот так менялась импульсная характеристика фильтра, пока он адаптировался к изменяющимся шуму и сигналу. На этом мы заканчиваем знакомство с адаптивными фильтрами. Впереди - реализация алгоритмов ЦОС в виде приложения и на аппаратной платформе.
Создание приложения в MATLAB
Две заключительные публикации будут посвящены реализации алгоритмов ЦОС, и сперва мы рассмотрим процесс создания графического приложения обработки аудио-файлов. Я хочу попробовать разработать набор фильтров нижних частот с различными границами полосы пропускания и иметь возможность переключаться между ними в процессе проигрывания аудио.
Для начала разработаем 8 фильтров нижних частот в приложении Filter Designer. Я выберу порядок фильтров равный 63, то есть буду получать наборы из шестидесяти четырёх коэффициентов, которые я пока что сохраню в рабочее пространство в виде численных векторов. Выполнив это действие 8 раз, я получу 8 наборов коэффициентов. На самом деле, учитывая, что разные наборы коэффициентов дают нам разные частотные отклики, нам нет необходимости честно реализовывать восемь структур фильтров. Нам хватит одной структуры, в которую мы будем подставлять нужный набор по желанию. Отклики фильтров варьируются от пропускающих только самые низкие частоты, до пропускающих практически весь спектр на частоте дискретизации 44.1 кГц.
Теперь, когда у нас есть восемь векторов num, удобно хранить их в виде одной переменной, а именно – матрицы. Осуществим вертикальную конкатенацию, то есть объединим строки. И сохраним нашу переменную nummat на жёстком диске в виде МАТ-файла с таким же именем. А также скопируем в директорию тестовые аудио-файлы и скрипт с основным алгоритмом.
В скрипте происходит потоковая обработка аудио с использованием системных объектов – о них я рассказывал в предыдущих публикациях. Помимо знакомых объектов для чтения и воспроизведения, мы также инициализируем объект симметричного КИХ-фильтра с входным портом для коэффициентов.
Мы будем выбирать набор коэффициентов из матрицы, отрисовывать АЧХ текущего фильтра, а также передавать коэффициенты объекту фильтра на каждом шаге цикла. Проверим обработку 7 фильтром.
А теперь установим 2 набор коэффициентов
Мы проверили работу алгоритма – можем приступать к созданию приложения. Графический интерфейс мы разработаем в App Designer. Сразу же сохраним наше приложение под уникальным именем. И теперь поместим на форму графические инструменты. Нам понадобятся оси для отрисовки АЧХ, уберём у них все подписи и добавим сетку. Пределы по осям зададим вручную.
Помимо этого, полезно иметь кнопки для управления воспроизведением и загрузкой. У нас будет четыре кнопки LOAD, PLAY, PAUSE И STOP. А для выбора фильтра установим переключатель Discrete Knob. Назовём его TONE, и укажем, какие дискретные значения он может принимать. Это будут числа от 1 до 8. Исходное значение укажем 8.
Внешний вид готов, можно переходить к коду.
Приложение будет обмениваться данными через переменные, которые здесь называются свойствами. Запишем имена свойств-переменных, которыми мы будем пользоваться внутри приложения.
И создадим свой первый callback, то есть набор команд, выполняемых каким-то действием или явлением. Сперва укажем, что будет происходить при запуске приложения startup function. Будет происходить загрузка матрицы фильтров.
Чтобы обмениваться значениями переменных между функциями, нужно добавить слово app в начале. Ну и также при запуске приложения мы хотим проинициализировать все системные объекты. Скопируем всё из скрипта, переименуем, но параметры менять не будем, пускай по-умолчанию считывается файл funky guitar, по 100 отсчётов.
После этого начнём создавать callback'и на нажатия кнопок. Первая кнопка - это загрузка файла. В скрипте у нас такого не было, поэтому тут мы сами запишем функцию uigetfile. Она открывает диалоговое окно, в котором мы будем выбирать аудио-файлы, mp-3, wav, или прочие, которые сможет прочитать объект ридера. Добавим выбранную папку с файлом на путь поиска, иначе будут ошибки. Ну и по-новой инициализируем объект AudioFileReader, на этот раз с выбранным именем файла.
Также я хочу, чтобы файл проигрывался бесконечно, пока я сам его не остановлю. Поэтому мы добавляем параметра PlayCount, и значение его ставим в бесконечность.
А запускать это самое проигрывание файла мы будем по кнопке Play. Сюда мы просто скопируем наш основной цикл while. Поправим имена переменных. Но для выхода из цикла нам понадобится логическая переменная – флаг остановки. И в том случае, если он становится истинным, мы должны прерывать цикл. А при нажатии кнопки play он всегда должен становиться ложным, чтобы цикл не вышел после одной итерации. Ну и нужно добавить волшебное слово drawnow, чтобы обновить форму и все callback'и. Без него из цикла не выйти.
Но пока что мы никак не выбираем коэффициенты. Номер строки мы хотели брать из положения переключателя. Поэтому добавляем callback на изменение его значения. Само значение запишем в num, но вот только оно хранится в строковом типе данных, и нужно его преобразовать в численное. И число это будет индексом строки для матрицы nummat.
Также добавим сюда отрисовку АЧХ. Функции plot надо явно указать, на какие оси помещать график. Оси у нас одни, UIAxes. Ну и пускай это всё происходит на запуске приложения так же.
Остались callback'и на паузу и стоп. При паузе мы просто устанавливаем флаг остановки в значение true. А при стопе мы, помимо этого, так же сбрасываем системные объекты, то есть отматываем на начало файла. Пауза работает, как и стоп. Проверим загрузку файла.
Мы рассмотрели процесс создания графического интерфейса пользователя для ваших алгоритмов ЦОС в MATLAB. Если у вас установлен MATLAB Compiler, вы можете скомпилировать независимое приложение и запускать его уже без MATLAB.
Обработка на Raspberry Pi
Последнее видео нашего плейлиста, и обработка на Raspberry Pi. Сразу отмечу, все наши публикации до этого держались в рамках MATLAB и парочки дополнительных тулбоксов, но в этой публикации мы впервые задействуем Simulink. Ну а в целом, это среда графического моделирования и динамической симуляции систем. Как показывает практика, очень удобная именно для реализации систем ЦОС на различных платформах.
Создадим новую модель Simulink. Здесь мы будем рисовать нашу систему в виде блок-схемы. Блоки мы находим в специальных библиотеках. Возьмём блок для чтения мультимедийных файлов. Укажем в нём интересующий нас файл. Теперь найдём блок для воспроизведения. Запустим модель на 4 секунды.
Мы можем отобразить прямо на схеме типы данных и размерности – наши блоки обмениваются матрицами типа double.
Теперь добавим блок дискретного КИХ-фильтра. Добавим ему отдельный вход для коэффициентов, а также укажем возможность покадровой обработки. Откуда же он будет получать значения коэффициентов? От прошлой публикации нам досталась матрица nummat. Мы можем хранить её в блоке Сonstant.
Вот только сама матрица ещё не загружена из МАТ-файла. Это мы можем быстро поправить. Но правильней было бы загружать переменную из файла каждый раз, когда мы хотим запустить модель. Сделать это можно при помощи callback модели. Запишем команду load в инициализацию. Теперь найдём блок variable selector, который поможет выбирать строки матрицы. А на его вход индекса пока что просто подадим какое-то значение константы.
У нас был выбран первый фильтр. Я хочу узнать, как он поменял спектр сигнала. Добавлю блок анализатора спектра, установим отображение действительной части спектра, и запустим модель. Теперь включим бесконечное воспроизведение, и попробуем поменять значение нижнего блока constant.
Менять вручную мне надоело, теперь я хочу чтобы коэффициенты менялись сами, по какому-то закону. В этом мне поможет блок источника сигнала Repeatign Sequence Stairs, повторяющаяся последовательность значений, и тут я просто укажу строки матрицы с первой по восьмую, и обратно. Период выдачи значений выберу 0,1 секунду.
Запускаем, видим ошибку. Конфликт периодов сигналов. Значения на входы фильтра приходят с разными периодами дискретизации. Если увеличить количество отсчётов сигнала на каждом шаге – конфликта не будет.У нас получился так называемый эффектавто УАУ, который будет ещё заметнее на сигнале с более равномерным спектром. Особенно на спектрограмме.
Для того, чтобы реализовать этот алгоритм на Raspberry Pi, нам потребуется установить пакет поддержки оборудования, вкладка Hardware Support Package. Там мы ищем Simulink Support Package для Raspberry Pi, и следуем инструкциям по установке. Важно отметить, что работать наши модели корректно будут на версиях MATLAB от 17b и выше. Вернёмся к модели.
В первую очередь давайте сохраним её под новым именем, назовём ее audio model raspberry. Также нам придётся поменять некоторые параметры в нашей модели, поэтому мы зайдём в основные настройки в раздел Hardware Implementation и выберем там тип платы. Остальные параметры таргета система выберет самостоятельно. Также нам придётся заменить наши блоки источника аудио и проигрывания, поэтому мы заходим в специализированную библиотеку Raspberry pi и достаём оттуда блоки audio file read и также блок ALSA Audio Playback на замену блокам Audio Device Writer и From Multimedia File.
Эти блоки мы убираем, а блоки для Raspberry pi поставим на их место. Также у них есть определённые особенности. Конкретно у блока ALSA Audio Playback, он понимает нас только типы данных в формате integer, то есть он не может принимать данные в double, как и блок Audio File Read также выдаёт только данные в формате Int16. Поэтому здесь нам понадобится блоки преобразование типов данных или Data Type Conversion. Первый будет преобразовывать из int16 в double, а второй будет преобразовывать из double обратно int16. После того, как мы проведём все преобразования, мы будем готовы запустить модель уже непосредственно на платформе.
Плата подключена к компьютеру, и теперь попробуем запустить ее в так называемом режиме External mode. Для этого нам надо будет поменять режим симуляции, но в первую очередь давайте поставим нашу знакомую гитару как тестовый аудиофайл. Теперь поменяем режим симуляции с Normal на External и запустим кнопку Run. Здесь у нас произойдёт автоматическая генерация кода, которая потом запустится на целевой цифровой платформе, то есть на Raspberry Pi.
Вот так вот выглядит автоматически сгенерированный код. Давайте посмотрим что же у нас будет происходить непосредственно на платформе.
Вот мы наблюдаем данные и слышим их с динамике на плате Raspberry Pi. Сейчас в режиме External модель передаёт данные на плату, плата передает данные обратно. Поэтому мы наблюдаем каждый спектр.
Также можем запустить модель и в режиме полностью автономном. Для это нам надо будет нажать кнопку build. Пока что отключим плату. Давайте поменяем аудиофайл, к примеру поставим там файл под названием Dance и нажмём кнопку Build. Также произойдет генерация кода, он отправится на Raspberry Pi. Теперь мы сможем отключить этот компьютер, и она будет работать в автономном режиме. Как можно будет услышать, музыка заиграла, обмен данными теперь не происходит. Если отсоединить плату, то теперь она абсолютно автономна. Единственный способ теперь – это просто убрать питание.
Комментарии
Привет!
Кажется я знаю более простой способ шумоподавления.
Если тут есть кто живой - откликнитесь.