В этом примере показано, как разработать и реализовать фильтр разделяемых изображений, который использует меньше аппаратных ресурсов, чем традиционный фильтр 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
функция для выполнения сингулярного разложения. The 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, квантованный фильтр, кажется, не разделяется. Для этого конкретного ядра фильтра можно экспериментировать с квантованным размером слова и обнаружить, что 51 бит точности необходимы в порядок, чтобы функция ранга вернула 1 после квантования. На самом деле, этот результат слишком консервативен из-за квантования почти нулевых значений в 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-D ядро Гауссова.
Чтобы увидеть реализацию разделяемого фильтра, откройте подсистему разделяемого фильтра, которая находится внутри подсистемы SepFiltHDL.
open_system([modelname '/SepFiltHDL/Separable Filter'],'force');
Эта подсистема выбирает вертикальные и горизонтальные векторы пикселей для фильтрации и выполняет операцию фильтрации.
Буфер Линии выводит столбец пикселей для каждого временного шага фильтра. Буфер Линии также заполняет ребра изображения. Эта модель использует метод заполнения: Constant
, со значением 0. Выходной сигнал shiftEnable обычно используется для управления регистром горизонтального сдвига, чтобы скомпилировать ядро 2-D пикселя. Однако для разделяемого фильтра необходимо работать в каждом направлении отдельно. Эта модель использует выход пикселя для вертикального фильтра и использует сигнал shiftEnable позже, чтобы создать горизонтальный вектор пикселя.
Разделенные горизонтальный и вертикальный фильтры симметричны, поэтому модель использует предварительный сумматор, чтобы уменьшить количество умножителей еще больше. После сумматора блок Gain умножает столбец пикселей на Hv
вектор. Параметр Gain установлен в Hv
и тип данных параметра fixdt(0,10)
. Получившийся тип выхода с полной точностью ufix18_En10
. Затем блок Sum завершает вертикальный фильтр. Блок Sum сконфигурирован в режиме полной точности. Выход является скаляром ufix21_En10
тип.
Существуют много опций конвейеризации, которые вы могли бы выбрать, но, поскольку этот проект прост, ручное конвейерирование является быстрым и простым. Добавление задержек в 2 цикла до и после умножителей Gain обеспечивает хорошую скорость при синтезе для FPGA. Задержка в 3 цикла после суммы позволяет также достаточно конвейерировать ее. Модель балансирует эти задержки на шине pixelcontrol и сигнале shiftEnable перед переходом к горизонтальному размерному фильтру.
Лучший способ создать регистр сдвига ширины ядра - использовать блок Tapped Delay, который смещается в скаляре и выводит значения регистров в качестве вектора. Для хороших результатов синтеза HDL используйте блок Tapped Delay внутри включенной подсистемы с блоком Synchronous marker.
Подсистема выхода Tapped Delay является вектором на 5 горизонтальных пикселей, готовым к горизонтальному компоненту разделяемого фильтра. Модель реализует аналогичный симметричный блок pre-add и Gain, на этот раз с Hh
как параметр. Затем блок Sum и аналогичная конвейеризация завершают горизонтальный фильтр. Окончательное значение отфильтрованного пикселя находится в полностью точном типе данных ufix34_En20
.
Много раз в обработке изображений вы хотели бы выполнить полную точность или хотя бы высокую точность арифметических операций, но затем вернуться к исходному типу входных данных пикселя для выхода. Эта подсистема возвращается к исходному типу пикселей с помощью блока Data Type Conversion, установленного в uint8
, с Nearest
округление и насыщение.
Блоки Vision HDL Toolbox заставляют выходные данные нуля, когда вывод недействителен, как показано на выходе шины pixelcontrol. Хотя это строго не требуется, такое поведение значительно облегчает проверку и отладку. Чтобы выполнить это поведение, модель использует блок Switch с набором блоков Constant, равным 0.
Реализация разделяемого фильтра 5x5 использует 3 умножителя в вертикальном направлении и 3 умножителя в горизонтальном направлении, в общей сложности 6 умножителей. Традиционный фильтр изображений обычно требует 25 умножителей для ядра 5x5. Однако блок Image Filter использует в своих интересах любую симметрию в ядре. В этом примере ядро имеет 8-стороннюю и 4-стороннюю симметрию, поэтому Image Filter использует только 5 умножителей. В целом есть экономия в множителях при реализации разделяемого фильтра, но в этом случае 2-D реализация аналогична.
Разделяемый фильтр использует 4 сумматора с двумя входами в каждом направлении, 2 для предварительного добавления плюс 2 в сумме, в общей сложности 8. Image Filter требует 14 сумматоров, с 10 добавлениями и 4 конечными сумматорами. Так что есть существенное сохранение в сумматорах.
Фильтр Изображения требует 25 регистров для регистра сдвига, в то время как разделяемый фильтр использует только 5 регистров для регистра сдвига. Каждый сумматор также требует регистра трубопровода так, чтобы было 8 для разделяемого случая и 14 для традиционного случая. Количество трубопроводов умножителей регистрирует шкалы в зависимости от количества умножителей.
Разделяемый фильтр использует меньше сумматоров и регистров, чем 2-D фильтр. Количество умножителей сходно между этими двумя фильтрами только потому, что реализация 2-D оптимизирует симметричные коэффициенты.
Получившиеся изображения из симуляции разделяемого фильтра и опорного Image Filter очень похожи. Используя настройки с фиксированной точкой в этом примере, различии между разделяемым фильтром и ссылкой фильтром никогда не превышает одного бита. Это различие составляет 0,1% разности или больше, чем 54 дБ PSNR между фильтрованными изображениями в целом.
Чтобы проверить и сгенерировать HDL-код, на который ссылаются в этом примере, необходимо иметь лицензию HDL- Coder™.
Чтобы сгенерировать HDL-код, используйте следующую команду.
makehdl('SeparableFilterHDL/SepFiltHDL')
Чтобы сгенерировать испытательный стенд, используйте следующую команду. Обратите внимание, что генерация испытательного стенда занимает много времени из-за большого размера данных. Уменьшите время симуляции перед генерацией испытательного стенда.
makehdltb('SeparableFilterHDL/SepFiltHDL')
Часть этой модели, которую можно реализовать на FPGA, является частью между блоками Frame To Pixels и Pixels To Frame. Подсистема SepFiltHDL включает как разделяемый алгоритм, так и традиционную реализацию 2-D для целей сравнения.
Теперь, когда у вас есть HDL-код, вы можете симулировать его в своем Симуляторе HDL. Автоматически сгенерированный испытательный стенд позволяет вам доказать, что симуляция Simulink и симуляция HDL совпадают.
Можно также синтезировать сгенерированный HDL-код в инструменте синтеза FPGA, таком как Xilinx Vivado. В Virtex-7 FPGA (xc7v585tffg1157-1) создание фильтра достигает тактовой частоты более 250 МГц.
Отчет об использовании показывает, что разделяемый фильтр использует меньше ресурсов, чем традиционный фильтр изображений. Различие в использовании ресурсов небольшая из-за оптимизации симметрии, примененной блоком Изображения Filter.
Фильтр в этом примере сконфигурирован для Гауссовой фильтрации, но другие типы фильтров также разделимы, включая некоторые, которые очень полезны. Средний фильтр, который имеет ядро с коэффициентами, которые все 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
Или ядро edge-detection 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
Разделяемость может также применяться к фильтрам, которые не используют multiply-add, таким как морфологические фильтры, где оператор является минимальным или максимальным.
Вы использовали линейную алгебру, чтобы определить, является ли ядро фильтра разделимым или нет, и если это так, вы научились разделять компоненты, используя svd
функция.
Вы исследовали эффекты квантования с фиксированной точкой и узнали, что важно работать с точными значениями при вычислении ранговых и сингулярных значений. Вы также узнали о важности поддержания усиления постоянного тока. Наконец, вы узнали, почему разделяемые фильтры могут быть реализованы более эффективно и как вычислить экономию.
[1] Eddins, S. «Separable convolution». Стив на обработке изображений (4 октября 2006).
[2] Eddins, S. «Separable convolution: Part 2». Стив на обработке изображений (28 ноября 2006).