exponenta event banner

Использование буфера строк для создания эффективных разделяемых фильтров

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

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

Разделяемый фильтр прост по концепции: если двумерное ядро фильтра может быть факторизовано в горизонтальную составляющую и вертикальную составляющую, то каждое направление можно вычислить отдельно с помощью одномерных фильтров. Такая факторизация может быть выполнена только для определенных типов ядер фильтров. Эти ядра называются разделяемыми, так как части могут быть разделены. С помощью линейной алгебры в MATLAB можно определить, какие ядра являются разделяемыми, а какие нелегкими. Математически два фильтра 1-D свертываются, чтобы сравняться с исходным ядром фильтра 2-D, но реализация разделяемого фильтра часто экономит аппаратные ресурсы.

Введение

Ниже показана система SeparableFilterHDL.slx. Подсистема SeptFiltHDL содержит разделяемый фильтр, а также блочную реализацию Image Filter эквивалентного ядра 2-D в качестве ссылки.

modelname = 'SeparableFilterHDL';
open_system(modelname);
set_param(modelname,'SampleTimeColors','on');
set_param(modelname,'SimulationCommand','Update');
set_param(modelname,'Open','on');
set(allchild(0),'Visible','off');

Определение разделяемых коэффициентов фильтра

Начните с определения назначения фильтра и вычислите ядро. В этом примере используется фильтр Гаусса размером 5x5 со стандартным отклонением 0,75. Этот фильтр оказывает размытое воздействие на изображения и часто используется для удаления шума и мелких деталей перед другими операциями фильтрации, такими как обнаружение краев. Обратите внимание, что ядро фильтра Гаусса циркулярно симметрично относительно центра.

Hg = fspecial('gaussian',[5,5],0.75)
Hg =

    0.0002    0.0033    0.0081    0.0033    0.0002
    0.0033    0.0479    0.1164    0.0479    0.0033
    0.0081    0.1164    0.2831    0.1164    0.0081
    0.0033    0.0479    0.1164    0.0479    0.0033
    0.0002    0.0033    0.0081    0.0033    0.0002

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

rankHg = rank(Hg)
rankHg =

     1

Для разделения ядра используйте svd функция для выполнения разложения сингулярных значений. svd функция возвращает три матрицы, [U,S,V], такой, что U*S*V' возвращает исходное ядро, Hg. Поскольку ядро имеет ранг 1, S содержит только один ненулевой элемент. Компоненты разделенного фильтра являются первым столбцом каждого из U и Vи сингулярное значение, разделенное между двумя векторами. Чтобы разделить сингулярное значение, умножьте оба вектора на квадратный корень из S. Необходимо изменить форму V чтобы Hh является горизонтальным вектором или строкой.

Дополнительные сведения о разделяемости фильтров см. в ссылках в нижней части этого примера.

[U,S,V]=svd(Hg)
Hv=abs(U(:,1)*sqrt(S(1,1)))
Hh=abs(V(:,1)'*sqrt(S(1,1)))
U =

   -0.0247   -0.9912   -0.1110    0.0673   -0.0000
   -0.3552    0.0737   -0.6054   -0.0431    0.7071
   -0.8640   -0.0301    0.4988    0.0619   -0.0000
   -0.3552    0.0737   -0.6054   -0.0431   -0.7071
   -0.0247   -0.0754    0.0761   -0.9939    0.0000


S =

    0.3793         0         0         0         0
         0    0.0000         0         0         0
         0         0    0.0000         0         0
         0         0         0    0.0000         0
         0         0         0         0    0.0000


V =

   -0.0247    0.1742    0.8521   -0.4929         0
   -0.3552    0.5980   -0.0716    0.1053    0.7071
   -0.8640   -0.4937    0.0199   -0.0968    0.0000
   -0.3552    0.5980   -0.0716    0.1053   -0.7071
   -0.0247   -0.1031    0.5131    0.8518   -0.0000


Hv =

    0.0152
    0.2188
    0.5321
    0.2188
    0.0152


Hh =

    0.0152    0.2188    0.5321    0.2188    0.0152

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

Hc = Hv * Hh;
equalTest = all(Hc(:)-Hg(:) < 5*max(eps(Hg(:))))
equalTest =

  logical

   1

Этот результат доказывает, что Hv и Hh может использоваться для воссоздания исходного ядра фильтра.

Параметры фиксированной точки

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

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

Hgfi = fi(Hg,0,10);
Hvfi = fi(Hv,0,10);
Hhfi = fi(Hh,0,10);

В этом случае 10-битный ответ с лучшей точностью для Hg имеет 11 дробных битов, в то время как Hv и Hh использовать только 10 дробных битов. Этот результат имеет смысл, поскольку Hv и Hh умножаются и добавляются вместе для создания исходного ядра.

Теперь необходимо проверить, является ли квантованное ядро по-прежнему рангом 1. С момента rank и svd функции не принимают типы с фиксированной точкой, необходимо преобразовать обратно в двойные. Эта операция не квантует результаты, если тип фиксированной точки меньше 53 бит, что является эффективным размером мантиссы в два раза.

rankDouble = rank(double(Hgfi))
rankDouble =

     3

Этот результат показывает, что квантование может оказывать драматическое влияние на разделяемость: поскольку ранг больше не равен 1, квантованный фильтр, по-видимому, не является разделяемым. Для этого конкретного ядра фильтра можно поэкспериментировать с квантованной длиной слова и обнаружить, что для того, чтобы ранговая функция возвращала 1 после квантования, необходим 51 бит точности. Фактически, этот результат является чрезмерно консервативным из-за квантования околонулевых значений в пределах rank функция.

Вместо расширения типа фиксированной точки до 51 бита добавьте аргумент допуска к rank функция для ограничения эффектов квантования.

rankDouble2048 = rank(double(Hgfi),1/2048)
rankDouble2048 =

     1

Этот результат показывает, что квантованное ядро имеет ранг 1 в пределах 11-битового дробного допуска. Таким образом, 11-битные разделенные коэффициенты в конце концов приемлемы.

Другой проблемой квантования является то, поддерживает ли фильтр с фиксированной точкой яркость плоского поля. Коэффициенты фильтра должны быть равны 1,0 для поддержания уровней яркости. Для нормализованного гауссова фильтра, такого как этот пример, сумма коэффициентов всегда равна 1,0, но эта сумма может быть удалена от 1,0 квантованием с фиксированной точкой. При обработке видео и изображений может быть критически важно поддерживать ровно 1.0, в зависимости от приложения. Если представить цепочку фильтров, каждый из которых поднимает средний уровень примерно на 1%, то кумулятивная ошибка может быть большой.

sumHg = sum( Hg(:) )
sumHgfi = sum( Hgfi(:) )
sumHg =

    1.0000


sumHgfi = 

     1

          DataTypeMode: Fixed-point: binary point scaling
            Signedness: Unsigned
            WordLength: 15
        FractionLength: 11

В этом случае суммы двойной точности Hg и фиксированная точка Hgfi действительно равны 1,0. Если в приложении важно поддерживать уровни яркости до абсолютных пределов, то, возможно, потребуется вручную скорректировать значения коэффициентов фильтра, чтобы сохранить сумму в 1.

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

Hcfi = fi(Hvfi * Hhfi,0,10,'fimath',fimath('RoundingMethod','Convergent'));
equalTest = all( Hcfi(:)==Hgfi(:) )
equalTest =

  logical

   1

Этот результат подтверждает, что фиксированная точка, отделенные коэффициенты достигают того же фильтра как 2-е Гауссовское ядро.

Реализация разделяемого фильтра

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

open_system([modelname '/SepFiltHDL/Separable Filter'],'force');

Эта подсистема выбирает вертикальные и горизонтальные векторы пикселей для фильтрации и выполняет операцию фильтрации.

Буфер строк выводит столбец пикселей для каждого временного шага фильтра. Буфер линий также размещает края изображения. В этой модели используется метод Padding: Constant, со значением 0. Выходной сигнал sheyEnable обычно используется для управления горизонтальным сдвиговым регистром для компиляции ядра 2-D пикселей. Однако для разделяемого фильтра необходимо работать в каждом направлении отдельно. Эта модель использует выходной пиксельный столбец для вертикального фильтра и использует сигнал sheyEnable для построения горизонтального пиксельного вектора.

Разделенные горизонтальный и вертикальный фильтры симметричны, поэтому модель использует предварительный сумматор, чтобы еще больше уменьшить число умножителей. После сумматора блок усиления умножает столбец пикселей на Hv вектор. Параметр Gain имеет значение Hv и тип данных параметра: fixdt(0,10). Результирующий тип вывода с полной точностью: ufix18_En10. Затем блок суммы завершает вертикальный фильтр. Блок Sum настроен в режиме полной точности. Выход является скаляром ufix21_En10 тип.

Существует много вариантов конвейерной обработки, которые вы можете выбрать, но поскольку эта конструкция проста, ручная конвейерная обработка является быстрой и простой. Добавление задержек в 2 цикла до и после множителей усиления обеспечивает хорошую скорость при синтезе для FPGA. Задержка в 3 цикла после суммирования также позволяет обеспечить достаточную конвейерность. Модель уравновешивает эти задержки на шине пиксельного управления и сигнале sheyEnable перед переходом к фильтру горизонтального размера.

Лучшим способом создания сдвигового регистра ширины ядра является использование блока задержки с отводом, который сдвигается в скаляре и выводит значения регистра в виде вектора. Для получения хороших результатов синтеза ЛПВП используйте блок задержки с отводом внутри включенной подсистемы с блоком маркера «Синхронный».

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

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

Блоки Vision HDL Toolbox приводят выходные данные к нулю, когда вывод недействителен, как указано на выходе шины пиксельного управления. Хотя это и не является строго обязательным, это делает тестирование и отладку намного проще. Для выполнения этого действия модель использует блок Switch с блоком Constant, имеющим значение 0.

Сравнение ресурсов

В разделяемой реализации фильтра 5 5 используются 3 умножителя в вертикальном направлении и 3 умножителя в горизонтальном направлении, в общей сложности 6 умножителей. Традиционный фильтр изображений обычно требует 25 множителей для ядра 5x5. Однако блок Image Filter использует любую симметрию в ядре. В этом примере ядро имеет 8-way и 4-way симметрию, поэтому фильтр изображения использует только 5 умножителей. В целом существует экономия в множителях при реализации разделяемого фильтра, но в этом случае реализация 2-D аналогична.

Разделяемый фильтр использует 4 сумматора с двумя входами в каждом направлении, 2 для предварительного сложения плюс 2 в сумме, в общей сложности 8. Для фильтра изображений требуется всего 14 сумматоров, 10 сумматоров предварительного добавления и 4 конечных сумматора. Таким образом, существует существенная экономия в сумматорах.

Фильтр изображения требует 25 регистров для сдвигового регистра, в то время как отделяемый фильтр использует только 5 регистров для сдвигового регистра. Каждый сумматор также требует конвейерного регистра, который равен 8 для отделяемого случая и 14 для традиционного случая. Количество регистров конвейера множителя масштабируется в зависимости от количества множителей.

Разделяемый фильтр использует меньше сумматоров и регистров, чем 2-D фильтр. Количество умножителей является одинаковым между двумя фильтрами только потому, что реализация 2-D оптимизирует симметричные коэффициенты.

Результаты моделирования

Полученные в результате моделирования изображения разделяемого фильтра и эталонного фильтра изображения очень похожи. Используя настройки с фиксированной точкой в этом примере, разница между разделяемым фильтром и эталонным фильтром никогда не превышает одного бита. Эта разница составляет 0,1% или более 54 дБ PSNR между отфильтрованными изображениями в целом.

Создание кода HDL

Для проверки и генерации кода HDL, на который ссылается этот пример, необходимо иметь лицензию HDL Coder™.

Для создания кода HDL используется следующая команда.

makehdl('SeparableFilterHDL/SepFiltHDL')

Для создания тестового стенда используется следующая команда. Следует отметить, что создание тестового стенда занимает много времени из-за большого размера данных. Сократите время моделирования перед созданием стенда.

makehdltb('SeparableFilterHDL/SepFiltHDL')

Часть этой модели, которую можно реализовать в FPGA, является частью между блоками «От кадра до пикселей» и «От пикселей до кадра». Подсистема SepFiltHDL включает в себя как разделяемый алгоритм, так и традиционную реализацию 2-D для целей сравнения.

Моделирование в имитаторе ЛПВП

Теперь, когда у вас есть код HDL, вы можете смоделировать его в вашем имитаторе HDL. Автоматически созданный стенд позволяет доказать, что моделирование Simulink и моделирование HDL совпадают.

Синтез для FPGA

Сгенерированный HDL-код также можно синтезировать в инструменте синтеза FPGA, таком как Xilinx Vivado. В Virtex-7 FPGA (xc7v585tffg1157-1) конструкция фильтра обеспечивает тактовую частоту более 250 МГц.

Отчет об использовании показывает, что разделяемый фильтр использует меньше ресурсов, чем традиционный фильтр изображений. Разница в использовании ресурсов невелика из-за оптимизации симметрии, применяемой блоком фильтра изображения.

Идти дальше

Фильтр в этом примере настроен для гауссовой фильтрации, но другие типы фильтров также являются разделяемыми, включая некоторые, которые очень полезны. Средний фильтр, который имеет ядро с коэффициентами, которые все 1/N, всегда отделяется.

Hm = ones(3)./9
rank(Hm)
Hm =

    0.1111    0.1111    0.1111
    0.1111    0.1111    0.1111
    0.1111    0.1111    0.1111


ans =

     1

Или ядро обнаружения краев Sobel:

Hs = [1 0 -1; 2 0 -2; 1 0 -1]
rank(Hs)
Hs =

     1     0    -1
     2     0    -2
     1     0    -1


ans =

     1

Или градиентные ядра, например:

Hgrad = [1 2 3; 2 4 6; 3 6 9]
rank(Hgrad)
Hgrad =

     1     2     3
     2     4     6
     3     6     9


ans =

     1

Разделяемость также может применяться к фильтрам, которые не используют умножение-сложение, например к морфологическим фильтрам, где оператор имеет значение min или max.

Заключение

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

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

Ссылки

[1] Эддинс, С. «Отделяемая свертка». Стив о обработке изображений (4 октября 2006).

[2] Эддинс, С. «Отделяемый сверток: часть 2». Стив о обработке изображений (28 ноября 2006).