doiplom-doc

advertisement
МИНИСТЕРСТВО ОБРАЗОВАНИЯ РЕСПУБЛИКИ БЕЛАРУСЬ
БЕЛОРУССКИЙ ГОСУДАСТРВЕННЫЙ УНИВЕРСИТЕТ
Факультет прикладной математики и информатики
Кафедра математического моделирования и управления
ЧЕКАН РОСТИСЛАВ ВЛАДИМИРОВИЧ
Дипломная работа студента 5 курса 6 группы
Руководитель
“Допустить к защите”
Дубров Борис Михайлович
Зав. Кафедры ММУ
Кандидат физико-математических
______________________
«__»_____________2010 г.
наук, доцент
Рецензент
Минск 2010
АННОТАЦИЯ
В данной дипломной работе изучаются различные реализации фильтра
Гаусса и производится их сравнительный анализ с параллелизацией
оптимального метода.
АНАТАЦЫЯ
У дадзенай дыпломнай працы вывучаюцца розныя рэалізацыі фільтра
Гаўса і робіцца іх параўнальны аналіз з параллелизацией аптымальнага метада.
ANNOTATION
Different implementations of the Gaussian filter are researched and
comparative analysis was made in this thesis including parallelization of the optimal
method.
РЕФЕРАТ
Отчёт по дипломной работе, 56 страниц, 7 источников, 2 приложения.
ФИЛЬТРА ГАУССА, РАЗМЫТИЕ ПО ГАУССУ, РЕКУРСИВНЫЙ
АЛГОРИТМ, ПАРАЛЛЕЛИЗАЦИЯ, OPENCL
Объект исследования – реализации фильтра Гаусса.
Цель работы – исследовать существующие реализации алгоритмов
фильтрации по Гауссу, выделить оптимальные, улучшить с помощью
параллелизации.
Метод исследования – аналитический метод, практическая реализация.
Результатом работы является программа, которая позволяет посмотреть
различные реализации фильтра Гаусса.
Содержание
ВВЕДЕНИЕ ....................................................................................................................................................................... 5
1. ФИЛЬТРЫ .................................................................................................................................................................... 7
1.1 ФИЛЬТР ГАУССА .................................................................................................................................................... 7
1.2 БАЗОВЫЙ СЛУЧАЙ ФИЛЬТРА ГАУССА .................................................................................................................. 9
1.3 ПРИМЕНЕНИЕ ФИЛЬТРА ГАУССА С ПОМОЩЬЮ ПРЕОБРАЗОВАНИЯ ФУРЬЕ.................................................... 13
1.3.1 Дискретное преобразование Фурье ......................................................................................................... 13
1.3.2 Быстрое преобразование Фурье ............................................................................................................... 15
1.4 РЕКУРСИВНЫЙ ФИЛЬТР ГАУССА ........................................................................................................................ 18
2. МЕТОДЫ ПАРАЛЛЕЛИЗАЦИИ. OPENCL ........................................................................................................ 22
2.1 ВЫБОР ПЛАТФОРМЫ ............................................................................................................................................ 22
2.2 OPENCL ................................................................................................................................................................ 24
2.3 ПРИМЕНЕНИЕ ТЕХНОЛОГИИ OPENCL К РЕКУРСИВНОМУ ФИЛЬТРУ ............................................................... 26
3. СРАВНИТЕЛЬНЫЙ АНАЛИЗ, УСЛОВИЯ ТЕСТИРОВАНИЯ, РЕЗУЛЬТАТЫ ........................................ 27
4. ВЫВОДЫ .................................................................................................................................................................... 36
ЗАКЛЮЧЕНИЕ ............................................................................................................................................................. 38
СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ ................................................................................................ 39
ПРИЛОЖЕНИЕ А. РУКОВОДСТВО ПОЛЬЗОВАТЕЛЯ ..................................................................................... 40
ПРИЛОЖЕНИЕ Б. ЛИСТИНГ ПРОГРАММЫ ...................................................................................................... 42
Введение
В настоящее время объемы различных видов мультимедиа информации
неуклонно растут. Производство фильмов, музыки и музыкальных клипов на
профессиональных студиях не сокращается, а количество любительских
записей постоянно увеличивается. С одной стороны это является следствием
большего оборота денежных средств в индустрии развлечений, а с другой возросшей доступность необходимых технических средств.
В связи с широким распространением сетей Интернет упрощается доступ
и обмен мультимедиа информацией. Следствием этого является проблема
поиска, обработки и анализа необходимой информации. В частности методы
распознавания образов и понимания сцены в настоящее время из-за отсутствия
эффективных универсальных алгоритмов применяются в узких предметных
областях. Для успешной обработки изображений в графическом дизайне и
моделировании необходимо иметь качественно быстрые фильтры начальной
обработки изображений.
Размытие изображений играет большую роль в современных областях
компьютерной графики. Размытие изображений часто бывает направлено на
имитацию близорукости (в тех случаях, когда близорукость становится
желательной или даже необходимой). Так, размытие отдельных частей
изображения часто используют из соображений цензуры. В ряде случаев
размытие является неотъемлемой частью различных
техник коррекции
изображения, направленных на устранение специфических дефектов (излишняя
детализация,
дефекты
сканирования,
царапины,
пыль).
Известно,
что
фотомодели и их фотографы используют специальные процедуры размытия
фотографических изображений для достижения эффекта устранения морщин.
Размытые изображения также лучше поддаются сжатию (так, при сохранении в
формате
JPEG графический файл имеет меньший размер, а также менее
выраженные
артефакты
компрессии).
Различные
техники
размытия
изображения доступны во всех современных графических редакторах. Одним
из наиболее важных алгоритмов размытия изображений является т. н. размытие
по Гауссу.
Разработка и анализ алгоритмов для решения проблемы фильтрации
цифровых сигналов будет являться предметом данной работы оптимальных по
скорости на больших объёмах данных.
1. Фильтры
Цифровой фильтр — любой фильтр, обрабатывающий цифровой сигнал с
целью выделения и/или подавления определённых частот этого сигнала. В
отличие от цифрового, аналоговый фильтр имеет дело с аналоговым сигналом,
его свойства не дискретны, соответственно передаточная функция зависит от
внутренних свойств составляющих его элементов. Особую роль среди
цифровых фильтров играют фильтры нижних частот (ФНЧ). ФНЧ – фильтр,
эффективно пропускающий частотный спектр сигнала ниже некоторой частоты
(частоты среза), и уменьшающий (или подавляющий) частоты сигнала выше
этой частоты. Степень подавления каждой частоты зависит от вида фильтра.
Широко
применяется
как
аппаратная
(на
основе
специализированных
микросхем или FPGA), так и программная (на базе процессоров общего
назначения или сигнальных процессоров) реализация фильтров нижних частот.
Мы же будем рассматривать различные варианты программной реализации
одного из ФНЧ - фильтра Гаусса.
1.1 Фильтр Гаусса
В электронике и обработке сигналов, фильтром Гаусса называют фильтр,
чья импульсная характеристика является функцией Гаусса. Импульсная
характеристика - выходной сигнал динамической системы как реакция на
входной сигнал, то есть наши данные (импульс). Гауссов фильтр спроектирован
таким образом, чтобы свести к минимуму отклонения от входных данных
переходной функции, реакцию системы на входное ступенчатое воздействие
при нулевых начальных условиях во время нарастания и спада.
Такое поведение связано с тем, что фильтр Гаусса имеет минимальную
групповую задержку, меру прохождения данных через ядро фильтра.
Математически, фильтр Гаусса представляет собой свёртку входного сигнала и
функции Гаусса. Это преобразование также известно как преобразование
Вейерштрасса.
Фильтр Гаусса обычно используется в цифровом виде для обработки
двумерных сигналов с целью снижения уровня шума. Визуально данных эффект
представляет собой лёгкое размытие, как при наблюдении через мутное стекло.
Стоит отметить весьма ограниченную скорость фильтра Гаусса при
реализации с помощью явного метода, особенно заметную на больших объёмах
данных.
1.2 Базовый случай фильтра Гаусса
Импульсная характеристика одномерного фильтра Гаусса может быть
представлена в виде:
g ( x) 
a

e  a x
2
.
А со среднеквадратичным отклонением:
1
e
2 
g ( x) 

x2
2 2
.
Для двумерного случая мы представляем фильтр как произведение двух
одномерных случаев, поэтому получим:
g ( x, y ) 
1
2 2

e
x2  y2
2 2
.
где x - расстояние от центра по горизонтальной оси, y - расстояние от центра по
вертикальной оси, σ- среднеквадратичное отклонение распределения гаусса.
Рассмотрим более общий случай. Результирующая формула будет иметь
следующий вид для n-мерного случая:

L( x1 ,..., xn , t ) 

 ...  f ( x
c
1
 u1 ,..., xn  un )g N (u1 ,..., un , t )du1...dun
u1  un 
Однако
для
реализации
данное
определение
может
быть
нецелесообразным в виду непрерывности. Поэтому в дальнейшем можем
сделать некоторые упрощения.
Так как гауссовское ядро обладает свойствами отделимости
g N ( x1 ,..., xn , t )  G( x1 , t )...G( xN , t ) ,
то n-мерная операция свёртки может быть разбита на множество одномерных
применений гауссова ядра для каждого измерения:

L( x1 ,..., xn , t ) 

 ...  f ( x  u ,..., x
c
u1   u n  
1
1
n
 un )G (u1 , t )du1...G(un , t )dun
,
где
1
G ( x, t ) 
2 t
e

x2
2t
.
и где t является корнем дисперсии и равно σ2. Свойство отделимости на
практике имеет большое значение, так как позволяет упростить вычисления и
привести их к одномерному случаю. Далее будем рассматривать именно его.
Для реализации одномерного шага сглаживания наиболее простым
способом является операция свёртки дискретного сигнала и гауссового ядра:
L ( x, t ) 

 f ( x  n)G(n, t ) ,
n
где
G ( n, t ) 
1
2 t
e

n2
2t
.
что в свою очередь может быть ограничено для сигнала с конечной импульсной
характеристикой:
L ( x, t ) 
M
 f ( x  n)G (n, t ) ,
n M
для M выбрано достаточно большим, что:

2  G(u, t )du  2
u m

 G(v,1)dv .
vM
t
Общий выбор выбора M заключается в создании зависимости её от
дисперсии, к примеру:
M  C  1  C t  1,
где С зачастую выбирается где-то между 3 и 6.
Использование заданного гауссова ядра может привести к проблемам
точности в случаях, когда важны точность и надёжность. При незначительной
роли погрешности при вычислениях (10 -
6
до 10 -
8
) , ошибки вносимые
ограничением ядра незначительны. Однако если точность важна, то есть более
лучшие альтернативы гауссову ядру как оконной функции, к примеру, оконные
функции Хэмминга, Блэкмана, Кайзера (см. [5]) будут меньше изменять спектр,
чем это сделает ядро гаусса. А так как функция Гаусса быстро убывает на
концах, то рассматривание значений на 
целесообразным.
больше 10 -
8
не является
1.3 Применение фильтра Гаусса с помощью преобразования Фурье
1.3.1 Дискретное преобразование Фурье
Итак, вспомним, что же такое преобразование Фурье – это интегральное
преобразование, которое ставит функцию вещественной переменой другую
функцию вещественной переменной и может быть записано в виде:

1
F ( w) 
2


f ( x)e  ixw dx .
Эта новая функция описывает коэффициенты («амплитуды») при
разложении
исходной
функции
на
элементарные
составляющие
—
гармонические колебания с разными частотами.
Не будем перечислять все свойства преобразования, а отметим только
важные для нас.
1. Формула обращения позволяет получить искомую функцию
f ( w) 
2.

1
2
ixw
F
(
w
)
e
dw .


Теорема о свёртке. Свёртка функций — операция, показывающая
«схожесть» одной функции с отражённой и сдвинутой копией другой. Пусть
f ,g :  
интегрируемые
—
относительно
называется функция:
две
функции вещественной
меры
Лебега.
Тогда
переменной,
их
свёрткой
( f  g )(t )   f ( )g (t   )d

.
f , g  L1 () , тогда
Тогда теорема о свёртке гласит: если
2 F ( f )  F ( g ) .
F ( f  g) 
Так как мы работаем с изображениями, то представим перечисленные
выше высказывания в дискретном виде. Прямое преобразование примет вид:
N 1
X k   xn e
2ikn
N
n 0
, k  0,..., N  1 .
Обратное преобразование:
1
xn 
N
N 1
X
k 0
k
e
2ikn
N
, n  0,..., N  1 .
Теорема о свёртке:
f ( x)  g ( x)  F ( x)  G ( x) .
Таким
образом,
мы
можем
выполнить
частотную
фильтрацию
изображения в частотной области. Это означает, что при частотной фильтрации
выполняются прямое и обратное пространственно-частотное преобразование, в
нашем случае двумерное дискретное преобразование Фурье (ДПФ) преобразует
изображение, заданное в пространственной координатной системе (x, y) , в
двумерное дискретное преобразование изображения, заданное в частотной
координатной системе (u, v). В соответствии с теоремой о свертке, свертка двух
функций
в
пространственной
области
может
быть
получена
ОДПФ
произведения их ДПФ.
Таким образом, алгоритм фильтрации по Гауссу в частотной области
будет выглядеть следующим образом:
 выполнить двумерное ДПФ входного изображения f(x,y) (подвергаемого
фильтрации) размером (N *M), получить F(u,v);
 вычислить передаточную характеристику фильтра Гаусса в частотной
области
 r 2 (u ,v )
H (u, v)  e
2 2
,
размер матрицы (N*M); выполнить децентрирование характеристики
H(u,v);
 выполнить поточечное умножение
S (u, v)  F (u , v)  H (u , v), u  [0, N  1], v  [0, M  1] ,
 выполнить ОДФП
На практике ДПФ крайне не эффективно, так как имеет сложность O(N2),
поэтому обычно применяют быстрое преобразование Фурье (БПФ).
1.3.2 Быстрое преобразование Фурье
Алгоритм быстрого преобразования Фурье (БПФ) - это оптимизированный
по скорости способ вычисления ДПФ. Основная идея заключается в двух
пунктах.
1. Необходимо разделить сумму ДПФ из N слагаемых на две суммы
по N/2 слагаемых, и вычислить их по отдельности. Для вычисления
каждой из подсумм, надо их тоже разделить на две и т.д.
2. Необходимо повторно использовать уже вычисленные слагаемые.
Применяют либо "прореживание по времени" (когда в первую сумму
попадают слагаемые с четными номерами, а во вторую - с нечетными), либо
"прореживание
по
частоте"
(когда
в
первую
сумму
попадают
первые N/2 слагаемых, а во вторую - остальные). Оба варианта равноценны. В
силу специфики алгоритма приходится применять только N, являющиеся
степенями 2. Рассмотрим случай прореживания по времени.
Введём определение поворачивающегося множителя:
.
Определим
еще
две
последовательности: {x[even]} и {x[odd]} через
последовательность {x} следующим образом:
X[even]n = X2n,
X[odd]n = X2n+1,
n = 0, 1,..., N/2-1.
Пусть к этим последовательностям применены ДПФ и получены
результаты в виде двух новых последовательностей {X[even]} и {X[odd]}
по N/2 элементов в каждой.
Утверждается, что элементы последовательности {X} можно выразить
через элементы последовательностей {X[even]} и {X[odd]} по формуле:
.
Согласно второй части формулы вышеописанной, получим:
.
ДПФ можно вычислить также по формуле:
.
Также по этой теореме видно, что отпадает необходимость хранить
вычисленные X[even]k и X[odd]k после использования при вычислении очередной
пары и одно вычисление
можно использовать для вычисления двух
элементов последовательности {X}.
На этом шаге будет выполнено N/2 умножений комплексных чисел. Если
мы применим ту же схему для вычисления последовательностей {X[even]}
и {X[odd]}, то каждая из них потребует N/4 умножений, итого еще N/2. Продолжая
далее в том же духе log2N раз, дойдем до сумм, состоящих всего из одного
слагаемого, так что общее количество умножений окажется равно (N/2)log2N,
что явно лучше, чем N2 умножений по формуле оригинального ДПФ. Если N
четно, то это разделение можно продолжать рекурсивно до тех пор, пока не
дойдем до двух точечного преобразования Фурье, которое вычисляется по
следующим формулам:
 X 0  x0  x1

.
 X 1  x0  x1
1.4 Рекурсивный фильтр Гаусса
Существует также ещё один метод фильтрации, основанный на
аппроксимации преобразования Фурье гауссового ядра, который широко
изложен в [1]. Покажем основную идею данного метода. Для этого представим
экспоненту в виде ряда Тэйлора, тогда ядро запишется в виде:
t2
g (t ) 

1
1
e 2 
  (t ) ,
a0  a 2 t 2  a 4 t 4  a6 t 6
2
где
a0 = 2.490895, a 2 = 1.466003, a 4 = -0.024393, a 0 = 0.178257.
Далее мы будем аппроксимировать не само гауссово ядро, а его
преобразование Фурье, которое хорошо известно:
G ( w)  e

 2 w2
2
 1 t
 F
e 2
 2

2


.

Тогда, подставляя вместо σ2 - q, мы получим выражение вида:
Gq ( w) 
A0
.
a0  a2 (qw) 2  a4 (qw) 4  a6 (qw) 6
И при s = jw:
Gq ( s ) 
A0
.
a0  ( a 2 q ) s  ( a 4 q 4 ) s 4  ( a6 q 6 ) s 6
2
2
Тогда выражение (выше), может быть разложено на множители
Gq (s ) = Gl (s) * Gr (s) :
GL ( s ) 
A1
(1.1668  qs )(3.20373  2.21567 qs  q 2 s 2 ) .
И
GR ( s ) 
A1
(1.1668  qs)(3.20373  2.21567qs  q 2 s 2 ) .
После этого для представления G (s) в H(z), моно было бы
воспользоваться стандартным билинейным преобразованием:
s
1  z 1
.
1  z 1
Но это бы привело к тому что в передаточной функции появились бы
нули, которых нам лучше избежать. Поэтому мы применим технику, описанную
в [6] и представим s 
1  z 1
для Gl (s) и s  z  1 для Gr (s) . Принимая T = 1,
T
получим:
H L ( z )  GL ( s) s 1 z 1 
A0
,
(1.1668  q(1  z ))(3.20373  2.21567q(1  z 1 )  q 2 (1  z 1 ) 2 )
1
и
H R ( z )  GR ( s) s  z 1 
A0
.
(1.1668  q( z  1))(3.20373  2.21567q( z  1)  q 2 ( z  1) 2 )
Оба выражения могут быть переписаны как стандартные полиномы
степени z и z 1 :
H L ( z) 
A2
,
b0  b1 z  b2 z 2  b3 z 3
H R ( z) 
A2
, где
b0  b1 z  b2 z 2  b3 z 3
1
1
b0  1.5725  2.44413 q  1.4281 q 2
b1  2.44413 q  2.85619 q 2  1.26661 q 3
b2  1.4281 q 2  1.26661 q 3
.
b3  0.422205 q 3
Тогда реализация [1] советует следующую фильтрующую стратегию.
Входные данные сначала фильтруются в прямом направлении
согласно
выражению для H L . Тогда результат этой фильтрации, назовём его w[n],
фильтруется в обратном направлении согласно выражению для H R , и
разностные выражения принимают следующий вид:
 Прямое:
w [n]  B  in [n] 
 обратное:
b1 w [n  1]  b2 w [n  2]  b3 w [n  3]
,
b0
out [n]  B  w [n] 
b1out [n  1]  b2 out [n  2]  b3 out [n  3]
.
b0
Оба выражения используют нормализационную константу, которая имеет
вид:
B  1
b1  b2  b3
.
b0
Стандартные рекомендации по выбору константы q представляют собой:
0.98711 0  0.96330

q
3.97156  4.14554 1  0.26891 0
, 0  2.5
,0.5   0  2.5
.
Итого сам фильтр сначала применяется по вертикали, а затем по
горизонтали. Средняя погрешность не превышает 10 3 , что составляет не более
0.68% согласно [1].
2. Методы параллелизации. OpenCL
Идея распараллеливания вычислений основана на том, что большинство
задач может быть разделено на набор меньших задач, которые могут быть
решены
одновременно.
Обычно
параллельные
вычисления
требуют
координации действий. Параллельные вычисления существуют в нескольких
формах: параллелизм на уровне битов, параллелизм на уровне инструкций,
параллелизм
данных,
параллелизм
задач.
Параллельные
вычисления
использовались много лет в основном в высокопроизводительных вычислениях,
но в последнее время к ним возрос интерес вследствие существования
физических ограничений на рост тактовой частоты процессоров. Параллельные
вычисления стали доминирующей парадигмой в архитектуре компьютеров, в
основном в форме многоядерных процессоров.
Писать программы для параллельных систем сложнее, чем для
последовательных, так как конкуренция за ресурсы представляет новый класс
потенциальных ошибок в программном обеспечении, среди которых состояние
гонки является самой распространённой. Если при вычислении не применяются
циклические (повторяющиеся) действия, то N вычислительных модулей никогда
не выполнят работу в N раз быстрее, чем один единственный вычислительный
модуль. А так как при реализации фильтра Гаусса применяется в основном
циклическая обработка данных, то разумно будет применить один из методов
параллелизации.
2.1 Выбор платформы
Наиболее распространенными видами параллелизации на данный момент
является многопоточность стандартными средствами центрального процессора
и многопоточность средствами GPU. К первому типу относится наиболее
универсальная
платформа под
названием
OpenMP. OpenMP
реализует
параллельные вычисления с помощью многопоточности, в которой «главный»
(master) поток создает набор подчиненных (slave) потоков и задача
распределяется между ними. Предполагается, что потоки выполняются
параллельно на машине с несколькими процессорами (количество процессоров
не обязательно должно быть больше или равно количеству потоков).
Количество создаваемых потоков может регулироваться как самой программой
при помощи вызова библиотечных процедур, так и извне, при помощи
переменных окружения.
Однако
процессоры
общего
назначения
менее
приспособлены
к
интенсивным арифметическим вычислениям, в отличие от GPU, которые
проектируются как раз для этих целей. Поэтому посмотрим в их сторону, что
приводит нас к следующему понятию.
GPGPU (англ. General-purpose graphics processing units — «GPU общего
назначения») — техника использования графического процессора видеокарты
для общих вычислений, которые обычно проводит центральный процессор. На
данный момент существуют следующие реализации:
 AMD FireStream — технология GPGPU, позволяющая программистам
реализовывать алгоритмы, выполнимые на графических процессорах
ускорителей ATI.
 CUDA
—
технология
GPGPU,
позволяющая
программистам
реализовывать на языке программирования Си алгоритмы, выполнимые
на графических процессорах ускорителей GeForce восьмого поколения и
старше (GeForce 8 Series, GeForce 9 Series, GeForce 200 Series), Nvidia
Quadro и Nvidia Tesla компании Nvidia. Технология CUDA разработана
компанией Nvidia.
 Direct3D 11 — вычислительный шейдер (англ. Compute Shader).
 OpenCL
является
языком
программирования
задач,
связанных
с
параллельными вычислениями на различных графических и центральных
процессорах.
Как видно, каждый производитель пытается продвинуть на рынок именно
свою технологию, и только OpenCL является открытым стандартом, который
поддерживается на большинстве современных ускорителях.
2.2 OpenCL
OpenCL (от англ. Open Computing Language — открытый язык
вычислений) — платформа для написания компьютерных программ, связанных
с параллельными вычислениями на различных графических
центральных
процессорах
(CPU).
В
фреймворк
OpenCL
(GPU) и
входят
язык
программирования, который базируется на стандарте C99, и интерфейс
программирования приложений (англ. API). OpenCL обеспечивает параллелизм
на уровне инструкций и на уровне данных и является реализацией техники
GPGPU. OpenCL является полностью открытым стандартом, его использование
не облагается лицензионными отчислениями.
Цель OpenCL состоит в том, чтобы дополнить OpenGL и OpenAL,
которые являются открытыми отраслевыми стандартами для трёхмерной
компьютерной графики и звука, пользуясь возможностями GPU. OpenCL
разрабатывается и поддерживается некоммерческим консорциумом Khronos
Group, в который входят много крупных компаний, включая Apple, AMD, Intel,
nVidia, Sun Microsystems, Sony Computer Entertainment и другие.
Машина,
на
которой
проводятся
вычисления
может
содержать
процессоры x86, x86-64, Itanium, SpursEngine (Cell), NVidia GPU, AMD GPU,
VIA (S3 Graphics) GPU. Для каждого из этих типов процессов существует свой
SDK (кроме разве что VIA), свой язык программирования и программная
модель.
OpenCL задумывался как технология для создания приложений, которые
могли бы исполняться в гетерогенной среде. Более того, он разработан так,
чтобы обеспечивать комфортную работу с такими устройствами, которые
сейчас находятся только в планах и даже с теми, которые еще никто не
придумал. Для координации работы всех этих устройств гетерогенной системе
всегда есть одно «главное» устройство, который взаимодействует со всеми
остальным посредствами OpenCL API. Такое устройство называется «хост», он
определяется вне OpenCL.
Поэтому OpenCL исходит из наиболее общих предпосылок, дающих
представление об устройстве с поддержкой OpenCL: так как это устройство
предполагается использовать для вычислений – в нем есть некий «процессор» в
общем смысле этого слова, назовём его «клиент». Нечто, что может исполнять
команды. Так как OpenCL создан для параллельных вычислений, то такой
процессор может, иметь средства параллелизма внутри себя (например,
несколько ядер одного CPU, несколько SPE процессоров в Cell). Также
элементарным способом наращивания производительности параллельных
вычислений является установка нескольких таких процессоров на устройстве (к
примеру, многопроцессорные материнские платы PC и т.д.). И естественно в
гетерогенной системе может быть несколько таких OpenCL-устройств (вообще
говоря, с различной архитектурой).
Кроме вычислительных ресурсов устройство имеет какой-то объем
памяти. Причем никаких требований к этой памяти не предъявляется, она может
быть как на устройстве, так и вообще быть размечена на ОЗУ хоста (как
например, это сделано у встроенных видеокарт).
Такое широкое понятие об устройстве позволяет не накладывать какихлибо ограничений на программы, разработанные для OpenCL. Эта технология
позволит разрабатывать как приложения, сильно оптимизированные под
конкретную
OpenCL,
архитектуру
так
и
те,
специфического
которые
будут
устройства,
поддерживающего
демонстрировать
стабильную
производительность на всех типах устройств (при условии эквивалентной
производительности этих устройств).
OpenCL предоставляет программисту низкоуровневый API, через который
он взаимодействует с ресурсами устройства. OpenCL API может либо напрямую
поддерживаться устройством, либо работать через промежуточный API (как в
случае NVidia: OpenCL работает поверх CUDA Driver API, поддерживаемый
устройствами), это зависит от конкретной реализации не описывается
стандартом.
2.3 Применение технологии OpenCL к рекурсивному фильтру
Так как само приложение написано с помощью универсального
фреймворка Qt, то, для использования OpenCL, была взята экспериментальная
разработка QtOpenCL (апрель 2010), которая берёт на себя все вопросы,
связанные с менеджментом ядер OpenCL, что даёт огромное удобство при
реализации.
Устройство хоста в нашем случае будет вычислять только необходимые
константы, настраивать окружение OpenCL и инициировать выполнение
программного кода в среде GPU (или CPU).
Итак, наш алгоритм состоит из
четырёх этапов, каждая пара которых
Начало
между собой идентична. Сперва, мы для
каждой строки изображения применяем
рекурсивный фильтр Гаусса, это делает
Проходим по строкам
для всех трёх цветовых компонент. Не
Транспонируем
трудно заметить, что будет присутствовать
два цикла, поэтому мы можем выделить
часть
кода
клиента,
который
Проходим по столбцам
будет
выполняться на одном из нескольких ядер
GPU.
Затем
транспонируем
Транспонируем
наше
изображение и применяем код клиента,
Конец
выделенного раньше (но в данном случае
это будет код для столбцов, а не строк). И затем обратно транспонируем
изображение.
Код клиента будет содержать все необходимые вычисления для расчёта
текущей точки. Так как параллельно будет запущено несколько ядер (N штук),
то общие вычисления будут в N раз быстрее. Это позволяет добиться огромного
прироста производительности.
3. Сравнительный анализ, условия тестирования, результаты
На практике я использовал release-сборку программы. Время измерялось
только в период выполнения операций фильтрования. Операции загрузки
изображений,
преобразований
их
во
внутренний
формат,
расчёт
первоначальных констант – это время не учитывалось.
Одна очень важная особенность техники GPGPU состоит в том, что
программа хоста, позволяет компилировать код клиента в момент выполнения
программы и оптимизировать его в зависимости от системы и оборудования.
Это очень важно, так например, не имея подходящего GPU, клиент может
выполняться на CPU, но также распараллелено. В моём случае, при
тестировании не удалось найти подходящий компьютер с новейшими GPU, но
можно утверждать, что код, выполненный на CPU, будет значительно быстрее
работать на GPU.
Итак, условия тестирования включают в себя:
 Процессор Intel Core 2 T7200 CPU @ 2.00 Ghz
 Оперативная память 2.00 Gb RAM
 Операционаня система Microsoft Windows XP Service Pack 3
 Видеокарта ATI Mobility Radeon X1600
Исходное 32ух-битное изображение использовалось следующих размеров
(в пикселях):
 256х256
 512х512
 1024х1024
 2048х2048
 4096х4096
Для разрешения 256х256 имеем следующие тесты (все результаты в
миллисекундах):
 Явный метод: 78, 63, 79
 С помощью FFT: 3859, 3965, 3842
 Рекурсивный: 47, 47, 32
 OpenCL: 15, 15, 15
OpenCL
Рекурсивный
Фурье
Явный
256x256
0
500
1000
1500
2000
2500
3000
3500
256x256
15
OpenCL
Рекурсивный
42
Фурье
3888,666667
Явный
73,33333333
Для изображения 512х512:
 Явный метод: 219, 218, 219
 С помощью FFT: 40015, 41134, 40187
 Рекурсивный: 172, 172, 157
 OpenCL: 47, 32, 31
4000
OpenCL
Рекурсивный
Фурье
Явный
512x512
0
5000 10000 15000 20000 25000 30000 35000 40000 45000
512x512
36,66666667
OpenCL
Рекурсивный
167
Фурье
40445,33333
Явный
218,6666667
Для изображения 1024х1024:
 Явный метод: 750, 765, 766
 С помощью FFT: 592422, 587886, 591135
 Рекурсивный: 454, 469, 454
 OpenCL: 141, 140, 141
OpenCL
Рекурсивный
Фурье
Явный
1024x1024
0
100000
200000
300000
400000
500000
600000
1024x1024
140,6666667
OpenCL
Рекурсивный
459
Фурье
590481
Явный
760,3333333
Как видим, фильтрация с помощью преобразования Фурье увеличивает
время выполнения программы более чем в 10 раз, при увеличении изображения
вдвое. Поэтому в дальнейшем я решил исключить данный тип фильтрации как
крайне неэффективный в данной реализации. Для изображения 2048х2048:
 Явный метод: 2969, 2984, 2985
 Рекурсивный: 1782, 1781, 1781
 OpenCL: 703, 703, 703
2048x2048
OpenCL
2048x2048
Рекурсивный
Явный
0
2048x2048
500
1000
1500
2000
2500
3000
Явный
Рекурсивный
OpenCL
2979,333333
1781,333333
703
Для изображения 4096х4096:
 Явный метод: 11922, 11937, 11954
 Рекурсивный: 6174, 6159, 6160
 OpenCL: 3109, 3094, 3172
4096x4096
OpenCL
4096x4096
Рекурсивный
Явный
0
4096x4096
2000
4000
6000
8000
10000
12000
Явный
Рекурсивный
OpenCL
11937,66667
6164,333333
3125
Так же покажем совместную диаграмму для всех типов изображения:
4096x4096
2048x2048
OpenCL
Рекурсивный
Фурье
1024x1024
Явный
512x512
256x256
0
100000
200000
300000
400000
500000
2048x2048
600000
256x256
512x512
1024x1024
4096x4096
OpenCL
15
36,66666667
140,6666667
703
3125
Рекурсивный
42
167
459
1781,333333
6164,333333
Фурье
3888,666667
40445,33333
590481
0
0
Явный
73,33333333
218,6666667
760,3333333
2979,333333
11937,66667
И для наглядности, без преобразования Фурье:
4096x4096
2048x2048
OpenCL
Рекурсивный
Явный
1024x1024
512x512
256x256
0
2000
4000
6000
8000
10000
12000
256x256
512x512
1024x1024
2048x2048
OpenCL
15
36,66666667
140,6666667
703
3125
Рекурсивный
42
167
459
1781,333333
6164,333333
73,33333333
218,6666667
760,3333333
2979,333333
11937,66667
Явный
4096x4096
Само исходное изображение и результат работы фильтра:
4. Выводы
Какие же можно сделать выводы? Первое, что хочется отметить – это
явное отставание с помощью фильтрации с применением преобразования
Фурье.
Вопреки ожиданиям, этот метод показал себя крайне неэффективно и
заочно выбыл из испытаний. Как такое могло случиться? Возможно, это связано
с огромным количеством циклических операций сложения в реализации метода.
Так же может повлиять постоянная адресация в различные сегменты памяти
ОЗУ, где влияет уже латентность(memory latency) самого физического
устройства. Возможно, из-за этой задержки сказывается такое поведение.
Так же в этой реализации присутствуют множество операций с
плавающей точкой. Хотя современные процессоры научились хорошо работать
в этом режиме – но в совокупности факторов это могло повлиять. Я так же
проводил тесты, задавая указание компилятору, внутренне представлять числа с
плавающей точкой в виде фиксированной. Но данный метод не сильно оправдал
себя. По результатам исследования я получил не более 6% прироста
производительности (~6400 миллисекунд против ~6000). В качестве вывода для
этого метода можно предположить следующее: без соответствующей хорошей
реализации
самого
преобразования
Фурье
мы
будем
худшую
производительность за счёт огромного количества проходов в циклах, где
осуществляются операции с плавающей точкой и неструктурные запросы в
оперативную память.
Явный метод показал себя, как и следовало ожидать, не в первых местах.
Однако в данном исследовании не на последнем месте. Здесь сказывается
большое количество операций - M*N*D*D, где М – изображение в ширину, N –
изображение в высоту, D – размер окна. Как видим, здесь каких либо
оптимизаций сделать не удастся, разве что за счёт уменьшения размера окна. Но
вопрос, зачем, если это уменьшает эффективность самого фильтра.
Намного лучше предыдущих показал себя рекурсивный фильтр Гаусса.
Здесь мы видим скорость и эффективность недоступную вышеописанным
претендентам. Хотя он и имеет сложность M*N*4, за счёт прохождения по
строкам изображения, по столбцам и дважды транспонирования, но показал сея
как вполне эффективный фильтр, который уже можно применять в прикладных
программах. Что можно улучшить в данном фильтре – так это избавиться от
транспонирования, что даст небольшой, но всё же прирост производительности.
И наконец, наиболее эффективный алгоритм нашего исследования –
рекурсивный фильтр Гаусса с использованием параллелизации на GPU и CPU
средствами OpenCL. Данный метод показал себя лучше всех даже на тестовом
двуядерном CPU. В некоторых тестах его производительность была более чем в
три раза выше ближайшего конкурента. А это очень много. Как можно
улучшить данный метод? Со стороны алгоритма, как и в рекурсивном случае,
избавиться от транспонирования, а со стороны аппаратного обеспечения –
повысить количество рабочих ядер. OpenCL позволяет задействовать все
имеющиеся в наличии ресурсы компьютера, как GPU, так и CPU, поэтому, как
говорится, чем больше – тем лучше. И данный метод уже может применяться на
больших объёмах данных с использованием мощных кластеров.
ЗАКЛЮЧЕНИЕ
1. В работе реализованы основные алгоритмы вычисления гауссова фильтра,
применяемого для сглаживания растровых изображений: явный метод,
метод быстрого преобразования Фурье, рекурсивный метод. Проведен
сравнительный анализ этих методов.
2. Рекурсивный метод является наиболее пригодным для эффективного
сглаживания растровых изображений, не смотря на то, что он вычисляет
результирующее изображение с некоторой погрешностью (0.69%). Такая
погрешность допустима в подавляющем большинстве практических задач
современной компьютерной графики.
3. Реализована параллелизация рекурсивного алгоритма с использованием
библиотеки
OpenCL.
повышено в 2.5-3 раза.
В результате быстродействие этого
метода
СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ
1. Young I., van Vliet L. Recursive implementation of the Gaussian filter. Signal
Processing, vol. 44. – Elsevier, 1995. – C.139-151.
2. Witkin A. Scale-space filtering. – Karlsruhe, Germany, 1983. – C. 10191021.
3. Shapiro L., Stockman G. Computer Vision. — Prentence Hall, 2001. —
C. 137-150.
4. Nixon M., Aguado A. Feature Extraction and Image Processing. — Academic
Press, 2008. — C. 88.
5. Сергиенко А. Б. Цифровая обработка сигналов. — Спб: Питер, 2006. —
С. 751.
6. Papoulis A. Signal Analysis. – McGraw-Hill, 1977. – C. 23.
7. Рудин У. Основы математического анализа. – М., 1976.
ПРИЛОЖЕНИЕ А. Руководство пользователя
Для корректной работы приложения необходимо установить OpenCL на
компьютер. После запуска программы необходимо открыть файл необходимо
открыть какой-либо файл с высотой и шириной степени двойки:
После в меню выбрать какой либо фильтр и нажать применить:
Так же можно посмотреть один из этапов преобразования Фурье выбрав в
меню соответствующий пункт:
ПРИЛОЖЕНИЕ Б. Листинг программы
#include <complex>
#include <algorithm>
#include <qmath.h>
#include <QtDebug>
#include <QTime>
#include <QImageReader>
#include "gauss.h"
#include "fourier.h"
#include "..\..\qtopencl\qclcontext.h"
#define idxc(x,y, stride) ((y)*(stride)*3+(x)*3 + c)
#define idx(x,y, stride) ((y)*(stride)+(x))
Gauss::Gauss():width(-1),height(-1),bpp(-1) {
static double predefinedMatrix[] = {
0.00000067,0.00002292,0.00019117,0.00038771,0.00019117,0.00002292,0.00000067,
0.00002292,0.00078633,0.00655965,0.01330373,0.00655965,0.00078633,0.00002292,
0.00019117,0.00655965,0.05472157,0.11098164,0.05472157,0.00655965,0.00019117,
0.00038771,0.01330373,0.11098164,0.22508352,0.11098164,0.01330373,0.00038771,
0.00019117,0.00655965,0.05472157,0.11098164,0.05472157,0.00655965,0.00019117,
0.00002292,0.00078633,0.00655965,0.01330373,0.00655965,0.00078633,0.00002292,
0.00000067,0.00002292,0.00019117,0.00038771,0.00019117,0.00002292,0.00000067
};
//we regenerate it by ourselves
genereateNativeGaussian(7, 0.84089642);
//generateFourierMatrix(5);
qDebug()<<"------------------------";
}
QVector<double> Gauss::genFou(int dim){
QVector<double> matrix(dim*dim);
double sigma = native.sigma;
for (int i = 0; i < dim; i++){
for (int j = 0; j < dim; j++){
double p = (-i * i - j * j)/(2.0 * sigma * sigma);
double e = exp(p);
matrix[ j*dim + i ] = e;
//qDebug()<<QString("%1 k=%2 p=%3 e=%4").arg(matrix[idx-1],
12, 'f',8).arg(k, 12, 'f',8).arg(p, 12, 'f',8).arg(e, 12, 'f',8);
}
}
return matrix;
}
QVector<double> Gauss::genereateNativeGaussian( int dimension, double sigma,
bool f )
{
qDebug()<<"generating gaussian matrix";
if (dimension == -1)
dimension = native.dim;
if (sigma == -1)
sigma = native.sigma;
QVector<double> matrix = QVector<double>(dimension*dimension);
int half_dim = dimension/2, idx = 0;
double sum = 0;
int odd = dimension % 2;
for (int i = -half_dim; i <= half_dim - 1 + odd; i++){
for (int j = -half_dim; j <= half_dim - 1 + odd; j++){
double k = 1.0 / sqrt(2.0 * M_PI) / sigma;
double p = (-i * i - j * j)/(2.0 * sigma * sigma);
double e = exp(p);
sum += (matrix[ idx++ ] = (!f?k:1) * e);
}
}
//normalize
for (uint i = 0; !f && i < (uint)dimension * dimension; i++)
matrix[i] /= sum;
native.dim
= dimension;
native.matrix
= matrix;
native.sigma
= sigma;
if (!"show matrix"){
showNative(native.matrix, native.dim);
QVector<double> im(dimension*dimension);
QVector<double> dRE(dimension*dimension);
QVector<double> dIM(dimension*dimension);
DFT2D<1>(dimension, dimension,0,native.matrix, im, dRE, dIM);
showNative(dRE, native.dim);
DFT2D<1>(dimension, dimension,1, dRE, dIM, native.matrix, im);
showNative(native.matrix, native.dim);
showNative(im, native.dim);
}
return native.matrix;
}
bool Gauss::openImage(QString filePath){
loaded = false;
QImageReader reader(filePath);
orig.image = reader.read();
if (!orig.image.isNull()){
loaded
= true;
orig.image = orig.image.convertToFormat(QImage::Format_RGB32);
width
= orig.image.width();
height
= orig.image.height();
bpp
= orig.image.depth();
initFourier();
} else
qDebug()<<reader.errorString();
return loaded;
}
QPixmap Gauss::originalPixmap(){
return QPixmap::fromImage(orig.image);
}
QPixmap Gauss::nativeFilter(){
return QPixmap::fromImage(native.image);
}
int Gauss::applyNativeFilter(){
QRgb *bits = (QRgb*)orig.image.bits();
int
qrgbCount = orig.image.byteCount() / sizeof(QRgb);
//TODO free
QRgb *gauss = new QRgb[qrgbCount];
QTime t;
t.start();
for (int x = 0; x < width; x++){
for (int y = 0; y < height; y++){
int pixelIndex = y * width + x;
double resultRed=0;
double resultGreen=0;
double resultBlue=0;
for (int x0 = x - native.dim / 2, xg=0; x0 < x + native.dim /
2; x0++, xg++){
if (x0 < 0 || x0 >= width)
continue;
for (int y0 = y - native.dim / 2, yg=0; y0 < y +
native.dim / 2; y0++, yg++){
if (y0 < 0 || y0 >= height)
continue;
double gaussian = native.matrix[ yg * native.dim +
xg ];
QRgb in = bits[y0 * width + x0];
resultRed
+= (double)qRed(in) * gaussian;
resultGreen += (double)qGreen(in) * gaussian;
resultBlue += (double)qBlue(in) * gaussian;
}
}
gauss[pixelIndex] = qRgb(resultRed, resultGreen, resultBlue);
}
}
int elapsed = t.elapsed();
native.image = QImage((uchar*)gauss, width, height, QImage::Format_RGB32);
return elapsed;
}
int Gauss::applyFourier(int type, bool inv, bool shift)
{
QTime t;
t.start();
if (type==0)
DFT2D<3>(fourier.width2, fourier.height2, inv, fourier.fRe,
fourier.fIm, fourier.FRe, fourier.FIm);
else
FFT2D<3>(fourier.width2, fourier.height2, inv, fourier.fRe,
fourier.fIm, fourier.FRe, fourier.FIm);
int elapsed = t.elapsed();
fix(fourier.width2, fourier.height2, fourier.FRe, fourier.gauss, shift,
false);
std::swap(fourier.fRe,fourier.FRe);
std::swap(fourier.fIm,fourier.FIm);
fourier.image = QImage((uchar*)fourier.gauss, fourier.width2,
fourier.height2, QImage::Format_RGB32);
return elapsed;
}
int Gauss::applyFourierFilter()
{
QTime t;
t.start();
QVector<double> mIm;
QVector<double> mRE;
QVector<double> mIM;
if ("fft gaussian kernel"){
genereateNativeGaussian(fourier.width2, native.sigma, true);
int d = native.dim * native.dim;
mIm = QVector<double>(d);
mRE = QVector<double>(d);
mIM = QVector<double>(d);
DFT2D<1>(native.dim, native.dim, 0, native.matrix, mIm, mRE, mIM);
}
FFT2D<3>(fourier.width2, fourier.height2, 0, fourier.fRe, fourier.fIm,
fourier.FRe, fourier.FIm);
std::swap(fourier.fRe,fourier.FRe);
std::swap(fourier.fIm,fourier.FIm);
if (!"do small kernel"){
QVector<double> matrix = native.matrix;
int dim = native.dim;
int cent = dim / 2;
for (int i = 0; i < dim/2 ; i++){
for (int j = 0; j < dim/2 ; j++){
for(int c = 0; c < 3; c++){
if ("fft g kernel"){
if ("do real"){
fourier.fRe[j*dim*3 + i*3 + c]
*= mRE[(j )*dim + i];
fourier.fRe[j*dim*3 + (dim - i - 1)*3
+ c]
*= mRE[(j )*dim + (dim - i - 1)];
fourier.fRe[(dim - j - 1)*dim*3 + i*3
+ c]
*= mRE[(dim - j - 1)*dim + i];
fourier.fRe[(dim - j - 1)*dim*3 + (dim
- i - 1)*3 + c]
*= mRE[(dim - j -1 )*dim + (dim - i - 1)];
}
+
+
-
+
-
if ("do imag"){
fourier.fIm[j*dim*3 + i*3 + c]
*= mIM[(j )*dim + i ];
fourier.fIm[j*dim*3 + (dim - i - 1)*3
c]
*= mIM[(j )*dim + (dim - i - 1)];
fourier.fIm[(dim - j - 1)*dim*3 + i*3
c]
*= mIM[(dim - j - 1)*dim + i];
fourier.fIm[(dim - j - 1)*dim*3 + (dim
i - 1)*3 + c]
*= mIM[(dim - j - 1)*dim + (dim - i - 1)];
}
} else {
if (!"do real"){
fourier.fRe[j*dim*3 + i*3 + c]
*= matrix[(j + cent)*dim + i + cent];
fourier.fRe[j*dim*3 + (dim - i)*3 + c]
*= matrix[(j + cent)*dim + (dim - i - cent)];
fourier.fRe[(dim - j - 1)*dim*3 + i*3
c]
*= matrix[(dim - j - cent)*dim + i + cent];
fourier.fRe[(dim - j - 1)*dim*3 + (dim
i)*3 + c] *= matrix[(dim - j - cent)*dim + (dim - i - cent)];
}
if (!"do imag"){
fourier.fIm[j*dim*3 + i*3 + c]
*= matrix[(j + cent)*dim + i + cent];
fourier.fIm[j*dim*3 + (dim - i)*3 + c]
*= matrix[(j + cent)*dim + (dim - i - cent)];
fourier.fIm[(dim - j - 1)*dim*3 + i*3
+ c]
*= matrix[(dim - j - cent)*dim + i + cent];
fourier.fIm[(dim - j - 1)*dim*3 + (dim
- i)*3 + c] *= matrix[(dim - j - cent)*dim + (dim - i - cent)];
}
}
}
}
}
} else{
//genereateNativeGaussian(fourier.width2, native.sigma);
int half = fourier.width2/2;
QVector<double> matrix = genFou(fourier.width2);
for (int i = 0; i < fourier.height2 ; i++){
for (int j = 0; j < fourier.width2 ; j++){
for(int c = 0; c <3; c++){
fourier.fRe[ idxc(j,i,fourier.width2)] *=
matrix[idx(j,i,fourier.width2)];
//fourier.fIm[ idxc(j,i,fourier.width2)] *=
matrix[idx(j,i,fourier.width2)];
}
}
}
}
FFT2D<3>(fourier.width2, fourier.height2, 1, fourier.fRe, fourier.fIm,
fourier.FRe, fourier.FIm);
int elapsed = t.elapsed();
fix(fourier.width2, fourier.height2, fourier.FRe ,fourier.gauss, false,
false);
std::swap(fourier.fRe,fourier.FRe);
std::swap(fourier.fIm,fourier.FIm);
fourier.image =
QImage((uchar*)fourier.gauss,fourier.width2,fourier.height2,QImage::Format_RGB32
);
return elapsed;
}
complex_t* Gauss::generateFourierMatrix(int n )
{
Q_UNUSED(n);
return 0;
}
QPixmap Gauss::fourierFilter()
{
return QPixmap::fromImage(fourier.image);
}
void Gauss::initFourier()
{
int width2 = fourier.width2 =
(int)pow(2,ceil(log(double(width))/log(2.0)));
int height2 = fourier.height2 =
(int)pow(2,ceil(log(double(height))/log(2.0)));
const QRgb* bits = (const QRgb*)orig.image.bits();
QRgb* gauss = fourier.gauss = new QRgb[width2*height2 ];
size_t length = width2*height2*3;
QVector<double> &fRe = fourier.fRe = QVector<double>(length);
if (width2<=2500){
fourier.fIm = QVector<double>(length);
fourier.FRe = QVector<double>(length);
fourier.FIm = QVector<double>(length);
}
size_t tt = width2*height2;
std::fill(gauss, gauss + tt, 0);
//set signal to the image
for(int x = 0; x < width2; x++)
for(int y = 0; y < height2; y++)
{
if (x>=width)
continue;
if (y>=height)
continue;
QRgb in = bits[y*width + x];
fRe[3*y*width2 + 3*x + 0] = qRed(in);
fRe[3*y*width2 + 3*x + 1] = qGreen(in);
fRe[3*y*width2 + 3*x + 2] = qBlue(in);
}
}
int Gauss::applyRecursive()
{
Q_ASSERT(native.sigma >= 0.5);
if (0 <= native.sigma && native.sigma <= 2.5)
recursive.q = 3.97156 - 4.14554 * sqrt(1.0 - 0.26891*native.sigma );
else if (native.sigma > 2.5)
recursive.q = -0.9633 + 0.98711 * native.sigma ;
double q = recursive.q;
double b0 = recursive.b0 = 1.57825 + (2.44413 * q) + (1.4281 * q * q);
double b1 = recursive.b1 = (2.44413 * q) + (2.85619 * q * q) + (1.26661 *
q * q * q);
double b2 = recursive.b2 = -((1.4281 * q * q) + (1.26661 * q * q * q));
double b3 = recursive.b3 = 0.422205 * q * q * q;
double B = recursive.B = 1.0 - ((b1 + b2 + b3)/b0);
QVector<double> in = fourier.fRe;
QTime t;
t.start();
recursiveStep(in.data(), B, b1, b2, b3, b0);//fixme
transpose(in.data());
recursiveStep(in.data(), B, b1, b2, b3, b0);
transpose(in.data());
int elapsed = t.elapsed();
fix(fourier.width2, fourier.height2, in ,fourier.gauss, false, false);
recursive.image =
QImage((uchar*)fourier.gauss,width,height,QImage::Format_RGB32);
return elapsed;
}
QPixmap Gauss::recursiveFilter()
{
return QPixmap::fromImage(recursive.image);
}
void Gauss::transpose( double * in )
{
Q_ASSERT_X(width == height,"Recursive filter","Image is not square!");
for (int y = 0; y < height; y++){
for (int x = y; x < width; x++){
std::swap(in[x*width*3 + y*3 + 0], in[y*width*3 + x*3 + 0]);
std::swap(in[x*width*3 + y*3 + 1], in[y*width*3 + x*3 + 1]);
std::swap(in[x*width*3 + y*3 + 2], in[y*width*3 + x*3 + 2]);
}
}
std::swap(width,height);
}
double * Gauss::recursiveStep( double * ind, double B, double b1, double b2,
double b3, double b0 )
{
for (int c = 0; c < 3; c++){
for (int y = 0; y < height; y++){
double *in = ind + y * width * 3;
double V = in[0 + c];
in[0*3 + c] = B * V + (b1 * V + b2 * V + b3*V) / b0;
in[1*3 + c] = B * in[1*3 + c] + (b1 * in[0*3 + c] + b2 * V +
b3 * V) / b0 ;
in[2*3 + c] = B * in[2*3 + c] + (b1 * in[1*3 + c] + b2 *
in[0*3 + c] + b3 * V) / b0 ;
for (int i = 3; i < width ; ++i)
in [i*3 + c] = B * in[i*3 + c] + (b1 * in[i*3 - 1*3
c] + b2 * in[i*3 - 2*3 + c] + b3 * in[i*3 - 3*3 + c]) / b0 ;
+
int r = width - 1;
V = in[r*3 + c];
in [r*3 - 0*3 + c] = B * V + (b1 * V + b2 * V + b3 * V ) / b0;
in [r*3 - 1*3 + c] = B * in[r*3 - 1*3 + c] + (b1 * in[r*3 + c]
+ b2 * V + b3 * V ) / b0;
in [r*3 - 2*3 + c] = B * in[r*3 - 2*3 + c] + (b1 * in[r*3 1*3 + c] + b2 * in[r*3 + c] + b3 * V ) / b0;
for (int i = r - 3; i >= 0; --i)
in[i*3 + c] = B * in[i*3 + c] + (b1 * in[i*3 + 1*3 + c]
+ b2 * in[i*3 + 2*3 + c] + b3 * in[i*3 + 3*3 + c]) / b0;
}
}
return 0;
}
int Gauss::applyRecursiveCL()
{
int width2 = fourier.width2, height2 = fourier.height2;
QCLContext context;
if (!context.create()){
qDebug()<<QCLContext::errorName(context.lastError());
return 0;
}
qDebug()<< context.defaultDevice().vendor()<<
context.defaultDevice().version();
QCLProgram program = context.buildProgramFromSourceFile(":/kernel");
if (program.isNull()){
qDebug()<<program.log()<<QCLContext::errorName(context.lastError());
return 0;
}
QCLKernel recursiveKernel = program.createKernel("recursive_kernel");
if (recursiveKernel.isNull()){
qDebug()<<QCLContext::errorName(context.lastError());
return 0;
}
QCLKernel transposeKernel = program.createKernel("transpose");
if (transposeKernel.isNull()){
qDebug()<<QCLContext::errorName(context.lastError());
return 0;
}
//transposeKernel.setGlobalWorkSize(width2, height2);
//transposeKernel.setLocalWorkSize(8, 8);
recursiveKernel.setGlobalWorkSize(height2, 3);
Q_ASSERT(native.sigma >= 0.5);
if (0 <= native.sigma && native.sigma <= 2.5)
recursive.q = 3.97156 - 4.14554 * sqrt(1.0 - 0.26891*native.sigma );
else if (native.sigma > 2.5)
recursive.q = -0.9633 + 0.98711 * native.sigma ;
double q = recursive.q;
float b0 = recursive.b0 = 1.57825 + (2.44413 * q) + (1.4281 * q * q);
float b1 = recursive.b1 = (2.44413 * q) + (2.85619 * q * q) + (1.26661 * q
* q * q);
float b2 = recursive.b2 = -((1.4281 * q * q) + (1.26661 * q * q * q));
float b3 = recursive.b3 = 0.422205 * q * q * q;
float B = recursive.B = 1.0 - ((b1 + b2 + b3)/b0);
QCLVector<float> vec = context.createVector<float>(width2*height2*3);
for (int i = 0; i < width2 * height2 * 3; i++)
vec[i] = fourier.fRe[i];
QTime t;
t.start();
vec.map();
recursiveKernel(vec, B, b1, b2, b3, b0, width2, height2);
transposeKernel(vec, width2, height2);
recursiveKernel(vec, B, b1, b2, b3, b0, width2, height2);
QCLEvent clevent = transposeKernel(vec);
clevent.waitForFinished();
vec.unmap();
int elapsed = t.elapsed();
QVector<float> in = QVector<float>(width2*height2*3);
vec.read(in.data(),width2*height2*3);
fix(fourier.width2, fourier.height2, in ,fourier.gauss, false, false);
recursive.image =
QImage((uchar*)fourier.gauss,width2,height2,QImage::Format_RGB32);
return elapsed;
}
void Gauss::showNative(QVector<double> mx, int dim)
{
QString s = "--------------------\n";
for (int i = 0; i < dim; i++){
for (int j = 0; j < dim; j++)
s.append(QString("%1 ").arg(mx[dim * i + j], 12, 'f',8));
s += "\n";
}
qDebug()<<s;
}
#ifndef __FFT_H__
#define __FFT_H__
#include <qmath.h>
#include <QRgb>
#ifdef DEBUG
#include <QtDebug>
#endif
#include "utils.h"
int prepare(int n, int m);
template<typename int D, typename C1, typename C2, typename C3, typename C4>
void FFT2D(int n, int m, bool inverse, C1& gRe, C2& gIm, C3& GRe, C4& GIm)
{
int l2n = 0, p = 1; //l2n will become log_2(n)
while(p < n) {p *= 2; l2n++;}
int l2m = 0; p = 1; //l2m will become log_2(m)
while(p < m) {p *= 2; l2m++;}
m= 1<<l2m; n= 1<<l2n; //Make sure m and n will be powers of 2, otherwise
you'll get in an infinite loop
//Erase all history of this array
for(int x = 0; x <m; x++){ //for each column
for(int y = 0; y < m; y++){ //for each
for(int c = 0; c < D; c++) //for
{
GRe[D * m * x + D * y + c]
GIm[D * m * x + D * y + c]
}
}
}
row
each color component
= gRe[D * m * x + D * y + c];
= gIm[D * m * x + D * y + c];
//Bit reversal of each row
int j;
for(int y = 0; y < m; y++){ //for each row
for(int c = 0; c < D; c++) //for each color component
{
j = 0;
for(int i = 0; i < n - 1; i++)
{
GRe[D * m * i + D * y + c] = gRe[D * m * j + D * y + c];
GIm[D * m * i + D * y + c] = gIm[D * m * j + D * y + c];
int k = n / 2;
while (k <= j) {j -= k; k/= 2;}
j += k;
}
}
}
//Bit reversal of each column
double tx = 0, ty = 0;
for(int x = 0; x < n; x++){ //for each column
for(int c = 0; c < D; c++) //for each color component
{
j = 0;
for(int i = 0; i < m - 1; i++)
{
if(i < j)
{
tx = GRe[D * m * x + D * i + c];
ty = GIm[D * m * x + D * i + c];
GRe[D * m * x + D * i + c] = GRe[D * m * x + D * j
+ c];
GIm[D * m * x + D * i + c] = GIm[D * m * x + D * j
+ c];
GRe[D * m * x + D * j + c] = tx;
GIm[D * m * x + D * j + c] = ty;
}
int k = m / 2;
while (k <= j) {j -= k; k/= 2;}
j += k;
}
}
}
//Calculate the FFT of the columns
for(int x = 0; x < n; x++){ //for each column
+
+
+
for(int c = 0; c < D; c++) //for each color component
{
//This is the 1D FFT:
double ca = -1.0;
double sa = 0.0;
int l1 = 1, l2 = 1;
for(int l=0;l<l2n;l++)
{
l1 = l2;
l2 *= 2;
double u1 = 1.0;
double u2 = 0.0;
for(int j = 0; j < l1; j++)
{
for(int i = j; i < n; i += l2)
{
int i1 = i + l1;
double t1 = u1 * GRe[D * m * x + D * i1
u2 * GIm[D * m * x + D * i1 + c];
double t2 = u1 * GIm[D * m * x + D * i1
u2 * GRe[D * m * x + D * i1 + c];
GRe[D * m * x + D * i1 + c] = GRe[D * m
D * i + c] - t1;
GIm[D * m * x + D * i1 + c] = GIm[D * m
D * i + c] - t2;
GRe[D * m * x + D * i + c] += t1;
GIm[D * m * x + D * i + c] += t2;
}
double z = u1 * ca - u2 * sa;
u2 = u1 * sa + u2 * ca;
u1 = z;
}
sa = sqrt((1.0 - ca) / 2.0);
if(!inverse) sa = -sa;
ca = sqrt((1.0 + ca) / 2.0);
}
}
}
//Calculate the FFT of the rows
for(int y = 0; y < m; y++) {//for each row
for(int c = 0; c < D; c++) //for each color component
{
//This is the 1D FFT:
double ca = -1.0;
double sa = 0.0;
int l1= 1, l2 = 1;
for(int l = 0; l < l2m; l++)
{
l1 = l2;
l2 *= 2;
double u1 = 1.0;
double u2 = 0.0;
for(int j = 0; j < l1; j++)
{
for(int i = j; i < n; i += l2)
{
int i1 = i + l1;
+ c]
+ c]
* x
* x
double t1 = u1 * GRe[D * m * i1 + D * y + c]
- u2 * GIm[D * m * i1 + D * y + c];
double t2 = u1 * GIm[D * m * i1 + D * y + c]
+ u2 * GRe[D * m * i1 + D * y + c];
GRe[D * m * i1 + D * y + c] = GRe[D * m * i
+ D * y + c] - t1;
GIm[D * m * i1 + D * y + c] = GIm[D * m * i
+ D * y + c] - t2;
GRe[D * m * i + D * y + c] += t1;
GIm[D * m * i + D * y + c] += t2;
}
double z = u1 * ca - u2 * sa;
u2 = u1 * sa + u2 * ca;
u1 = z;
}
sa = sqrt((1.0 - ca) / 2.0);
if(!inverse) sa = -sa;
ca = sqrt((1.0 + ca) / 2.0);
}
}
}
int d;
if(inverse) d = n; else d = m;
for(int x = 0; x < n; x++) for(int y = 0; y < m; y++) for(int c = 0; c <
D; c++) //for every value of the buffers
{
GRe[D * m * x + D * y + c] /= d;
GIm[D * m * x + D * y + c] /= d;
}
}
/********************************************/
double get_cos(double a);
double get_sin(double a);
template<typename int D, typename C1, typename C2, typename C3, typename C4>
void DFT2D(int n, int m, bool inverse, C1& gRe, C2& gIm, C3& GRe, C4& GIm){
std::vector<double> Gr2(m * n * D);
std::vector<double> Gi2(m * n * D); //temporary buffers
//calculate the fourier transform of the columns
for(int x = 0; x < n; x++){
for(int c = 0; c < D; c++)
{
//This is the 1D DFT:
for(int w = 0; w < m; w++)
{
Gr2[m * D * x + D * w + c] =Gi2[m * D * x + D * w + c] =
0;
for(int y = 0; y < m; y++)
{
double a= 2 * M_PI * w * y / double(m);
if(!inverse)a = -a;
double ca = get_cos(a);
double sa = get_sin(a);
Gr2[m * D * x + D * w + c] += gRe[m * D * x + D *
y + c] * ca - gIm[m * D * x + D * y + c] * sa;
Gi2[m * D * x + D * w + c] += gRe[m * D * x + D *
y + c] * sa + gIm[m * D * x + D * y + c] * ca;
}
}
}
}
//calculate the fourier transform of the rows
for(int y = 0; y < m; y++){
for(int c = 0; c < D; c++)
{
//This is the 1D DFT:
for(int w = 0; w < n; w++)
{
GRe[m * D * w + D * y + c] = GIm[m * D * w + D * y + c]
= 0;
for(int x = 0; x < n; x++)
{
double a = 2 * M_PI * w
if(!inverse)a = -a;
double ca = get_cos(a);
double sa = get_sin(a);
GRe[m * D * w + D * y +
y + c] * ca - Gi2[m * D * x + D * y + c] * sa;
GIm[m * D * w + D * y +
y + c] * sa + Gi2[m * D * x + D * y + c] * ca;
}
if(inverse)
{
GRe[m * D * w + D * y +
GIm[m * D * w + D * y +
}
else{
GRe[m * D * w + D * y +
GIm[m * D * w + D * y +
}
}
}
}
}
* x / double(n);
c] += Gr2[m * D * x + D *
c] += Gr2[m * D * x + D *
c] /= n;
c] /= n;
c] /= m;
c] /= m;
struct ColorRGB{
ColorRGB():r(0),g(0),b(0){};
int r,g,b;
};
template<typename T>
void fix(int n, int m, T g, QRgb * G, bool shift, bool neg128){
Q_UNUSED(neg128);
for(int x = 0; x < n; x++)
for(int y = 0; y < m; y++)
{
int x2 = x, y2 = y;
if(shift) {x2 = (x + n / 2) % n; y2 = (y + m / 2) % m;}
//Shift corners to center
ColorRGB c;
//calculate c values out of the floating point buffer
c.r = int(g[3 * m * x2 + 3 * y2 + 0]);
c.g = int(g[3 * m * x2 + 3 * y2 + 1]);
c.b = int(g[3 * m * x2 + 3 * y2 + 2]);
//negative colors give confusing effects so set them to 0
if(c.r < 0) c.r = 0;
if(c.g < 0) c.g = 0;
if(c.b < 0) c.b = 0;
//set c components higher than 255 to 255
if(c.r > 255) c.r = 255;
if(c.g > 255) c.g = 255;
if(c.b > 255) c.b = 255;
//plot the pixel
G[(x)*m+y] = qRgb(c.r,c.g,c.b);
}
}
#endif
Download